You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ji...@apache.org on 2015/07/25 01:37:54 UTC
[01/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Repository: mesos
Updated Branches:
refs/heads/master 50696fa2f -> 963513722
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/routing_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/routing_tests.cpp b/src/tests/routing_tests.cpp
deleted file mode 100644
index e4f1bcf..0000000
--- a/src/tests/routing_tests.cpp
+++ /dev/null
@@ -1,1416 +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 <signal.h>
-#include <unistd.h>
-
-#include <linux/version.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include <gtest/gtest.h>
-
-#include <process/clock.hpp>
-#include <process/gtest.hpp>
-
-#include <stout/foreach.hpp>
-#include <stout/gtest.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/ip.hpp>
-#include <stout/mac.hpp>
-#include <stout/net.hpp>
-#include <stout/stringify.hpp>
-
-#include "linux/routing/handle.hpp"
-#include "linux/routing/route.hpp"
-#include "linux/routing/utils.hpp"
-
-#include "linux/routing/diagnosis/diagnosis.hpp"
-
-#include "linux/routing/filter/basic.hpp"
-#include "linux/routing/filter/handle.hpp"
-#include "linux/routing/filter/icmp.hpp"
-#include "linux/routing/filter/ip.hpp"
-
-#include "linux/routing/link/link.hpp"
-
-#include "linux/routing/queueing/fq_codel.hpp"
-#include "linux/routing/queueing/htb.hpp"
-#include "linux/routing/queueing/ingress.hpp"
-#include "linux/routing/queueing/statistics.hpp"
-
-using namespace process;
-
-using namespace routing;
-using namespace routing::filter;
-using namespace routing::queueing;
-
-using std::endl;
-using std::set;
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-static const string TEST_VETH_LINK = "veth-test";
-static const string TEST_PEER_LINK = "veth-peer";
-
-
-class RoutingTest : public ::testing::Test
-{
-protected:
- virtual void SetUp()
- {
- ASSERT_SOME(routing::check())
- << "-------------------------------------------------------------\n"
- << "We cannot run any routing tests because either your libnl\n"
- << "library or kernel is not new enough. You can either upgrade,\n"
- << "or disable this test case\n"
- << "-------------------------------------------------------------";
- }
-};
-
-
-// Tests that require setting up virtual ethernet on host.
-class RoutingVethTest : public RoutingTest
-{
-protected:
- virtual void SetUp()
- {
- RoutingTest::SetUp();
-
- // Clean up the test links, in case it wasn't cleaned up properly
- // from previous tests.
- link::remove(TEST_VETH_LINK);
-
- ASSERT_SOME_FALSE(link::exists(TEST_VETH_LINK));
- ASSERT_SOME_FALSE(link::exists(TEST_PEER_LINK));
- }
-
- virtual void TearDown()
- {
- link::remove(TEST_VETH_LINK);
- }
-};
-
-
-TEST_F(RoutingTest, PortRange)
-{
- Try<ip::PortRange> ports = ip::PortRange::fromBeginEnd(1, 0);
- EXPECT_ERROR(ports);
-
- ports = ip::PortRange::fromBeginEnd(4, 11);
- EXPECT_ERROR(ports);
-
- ports = ip::PortRange::fromBeginEnd(4, 7);
- ASSERT_SOME(ports);
- EXPECT_EQ(4u, ports.get().begin());
- EXPECT_EQ(7u, ports.get().end());
- EXPECT_EQ(0xfffc, ports.get().mask());
- EXPECT_EQ("[4,7]", stringify(ports.get()));
-
- ports = ip::PortRange::fromBeginEnd(10, 10);
- ASSERT_SOME(ports);
- EXPECT_EQ(10u, ports.get().begin());
- EXPECT_EQ(10u, ports.get().end());
- EXPECT_EQ(0xffff, ports.get().mask());
- EXPECT_EQ("[10,10]", stringify(ports.get()));
-
- ports = ip::PortRange::fromBeginMask(20, 0xffff);
- ASSERT_SOME(ports);
- EXPECT_EQ(20u, ports.get().begin());
- EXPECT_EQ(20u, ports.get().end());
- EXPECT_EQ(0xffff, ports.get().mask());
- EXPECT_EQ("[20,20]", stringify(ports.get()));
-
- ports = ip::PortRange::fromBeginMask(1024, 0xfff8);
- ASSERT_SOME(ports);
- EXPECT_EQ(1024u, ports.get().begin());
- EXPECT_EQ(1031u, ports.get().end());
- EXPECT_EQ(0xfff8, ports.get().mask());
- EXPECT_EQ("[1024,1031]", stringify(ports.get()));
-}
-
-
-TEST_F(RoutingTest, RouteTable)
-{
- Try<vector<route::Rule> > table = route::table();
- EXPECT_SOME(table);
-
- Result<net::IP> gateway = route::defaultGateway();
- EXPECT_FALSE(gateway.isError());
-}
-
-
-TEST_F(RoutingTest, LinkIndex)
-{
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
-
- foreach (const string& link, links.get()) {
- EXPECT_SOME_NE(0, link::index(link));
- }
-
- EXPECT_NONE(link::index("not-exist"));
-}
-
-
-TEST_F(RoutingTest, LinkName)
-{
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
-
- foreach (const string& link, links.get()) {
- EXPECT_SOME_NE(0, link::index(link));
- EXPECT_SOME_EQ(link, link::name(link::index(link).get()));
- }
-}
-
-
-TEST_F(RoutingTest, LinkStatistics)
-{
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
-
- foreach (const string& link, links.get()) {
- Result<hashmap<string, uint64_t> > statistics = link::statistics(link);
-
- ASSERT_SOME(statistics);
- EXPECT_TRUE(statistics.get().contains("rx_packets"));
- EXPECT_TRUE(statistics.get().contains("rx_bytes"));
- EXPECT_TRUE(statistics.get().contains("tx_packets"));
- EXPECT_TRUE(statistics.get().contains("tx_bytes"));
- }
-
- EXPECT_NONE(link::statistics("not-exist"));
-}
-
-
-TEST_F(RoutingTest, LinkExists)
-{
- Try<set<string> > links = net::links();
- ASSERT_SOME(links);
-
- foreach (const string& link, links.get()) {
- EXPECT_SOME_TRUE(link::exists(link));
- }
-
- EXPECT_SOME_FALSE(link::exists("not-exist"));
-}
-
-
-TEST_F(RoutingTest, Eth0)
-{
- Result<string> eth0 = link::eth0();
- EXPECT_FALSE(eth0.isError());
-
- if (eth0.isSome()) {
- ASSERT_SOME_TRUE(link::exists(eth0.get()));
- }
-}
-
-
-TEST_F(RoutingTest, Lo)
-{
- Result<string> lo = link::lo();
- EXPECT_FALSE(lo.isError());
-
- if (lo.isSome()) {
- ASSERT_SOME_TRUE(link::exists(lo.get()));
- }
-}
-
-
-TEST_F(RoutingTest, INETSockets)
-{
- Try<vector<diagnosis::socket::Info> > infos =
- diagnosis::socket::infos(AF_INET, diagnosis::socket::state::ALL);
-
- EXPECT_SOME(infos);
-
- foreach (const diagnosis::socket::Info& info, infos.get()) {
- // Both source and destination IPs should be present since
- // 'AF_INET' is asked for.
- EXPECT_SOME(info.sourceIP);
- EXPECT_SOME(info.destinationIP);
- }
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkCreate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- EXPECT_SOME_NE(0, link::index(TEST_VETH_LINK));
- EXPECT_SOME_NE(0, link::index(TEST_PEER_LINK));
-
- // Test the case where the veth (with the same name) already exists.
- EXPECT_SOME_FALSE(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkRemove)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::remove(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(link::remove(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(link::remove(TEST_PEER_LINK));
-}
-
-
-// An old glibc might not have this symbol.
-#ifndef CLONE_NEWNET
-#define CLONE_NEWNET 0x40000000
-#endif
-
-
-// Entry point of the child process (used in clone()).
-static int child(void*)
-{
- // Wait to be killed.
- while (true) {
- sleep(1);
- }
-
- // Should not reach here.
- ABORT("Child process should not reach here");
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkCreatePid)
-{
- // Stack used in the child process.
- unsigned long long stack[32];
-
- pid_t pid = ::clone(child, &stack[31], CLONE_NEWNET | SIGCHLD, NULL);
- ASSERT_NE(-1, pid);
-
- // In parent process.
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, pid));
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
-
- // The peer should not exist in parent network namespace.
- EXPECT_SOME_FALSE(link::exists(TEST_PEER_LINK));
-
- // TODO(jieyu): Enter the child network namespace and make sure that
- // the TEST_PEER_LINK is there.
-
- EXPECT_SOME_NE(0, link::index(TEST_VETH_LINK));
-
- // Kill the child process.
- ASSERT_NE(-1, kill(pid, SIGKILL));
-
- // Wait for the child process.
- int status;
- EXPECT_NE(-1, waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkWait)
-{
- AWAIT_READY(link::removed(TEST_VETH_LINK));
-
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- Future<Nothing> removed = link::removed(TEST_VETH_LINK);
- EXPECT_TRUE(removed.isPending());
-
- ASSERT_SOME_TRUE(link::remove(TEST_VETH_LINK));
- AWAIT_READY(removed);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkSetUp)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- EXPECT_SOME_FALSE(link::isUp(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::setUp(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::isUp(TEST_VETH_LINK));
-
- EXPECT_SOME_FALSE(link::isUp(TEST_PEER_LINK));
- EXPECT_SOME_TRUE(link::setUp(TEST_PEER_LINK));
- EXPECT_SOME_TRUE(link::isUp(TEST_PEER_LINK));
-
- EXPECT_NONE(link::isUp("non-exist"));
- EXPECT_SOME_FALSE(link::setUp("non-exist"));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkSetMAC)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- uint8_t bytes[6] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc};
-
- EXPECT_SOME_TRUE(link::setMAC(TEST_VETH_LINK, net::MAC(bytes)));
- EXPECT_SOME_TRUE(link::setMAC(TEST_PEER_LINK, net::MAC(bytes)));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
-
- ASSERT_SOME(mac);
- EXPECT_EQ(mac.get(), net::MAC(bytes));
-
- mac = net::mac(TEST_PEER_LINK);
-
- ASSERT_SOME(mac);
- EXPECT_EQ(mac.get(), net::MAC(bytes));
-
- EXPECT_SOME_FALSE(link::setMAC("non-exist", net::MAC(bytes)));
-
- // Kernel will reject a multicast MAC address.
- uint8_t multicast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-
- EXPECT_ERROR(link::setMAC(TEST_VETH_LINK, net::MAC(multicast)));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_LinkMTU)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- EXPECT_SOME_TRUE(link::setMTU(TEST_VETH_LINK, 10000));
-
- Result<unsigned int> mtu = link::mtu(TEST_VETH_LINK);
- ASSERT_SOME(mtu);
- EXPECT_EQ(10000u, mtu.get());
-
- EXPECT_NONE(link::mtu("not-exist"));
- EXPECT_SOME_FALSE(link::setMTU("not-exist", 1500));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IngressQdisc)
-{
- // Test for a qdisc on a nonexistent interface should fail.
- EXPECT_SOME_FALSE(ingress::exists("noSuchInterface"));
-
- EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- // Interface exists but does not have an ingress qdisc.
- EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
-
- // Interfaces without qdisc established no data.
- EXPECT_NONE(ingress::statistics(TEST_VETH_LINK));
- EXPECT_NONE(ingress::statistics(TEST_PEER_LINK));
-
- // Try to create an ingress qdisc on a nonexistent interface.
- EXPECT_ERROR(ingress::create("noSuchInterface"));
-
- // Create an ingress qdisc on an existing interface.
- EXPECT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- // Interface exists and has an ingress qdisc.
- EXPECT_SOME_TRUE(ingress::exists(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
-
- // Interfaces which exist return at least the core statisitcs.
- Result<hashmap<string, uint64_t>> stats = ingress::statistics(TEST_VETH_LINK);
- ASSERT_SOME(stats);
- EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
- EXPECT_TRUE(stats.get().contains(statistics::BYTES));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
- EXPECT_TRUE(stats.get().contains(statistics::QLEN));
- EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
- EXPECT_TRUE(stats.get().contains(statistics::DROPS));
- EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
- EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
-
- // Interface without qdisc returns no data.
- EXPECT_NONE(ingress::statistics(TEST_PEER_LINK));
-
- // Try to create a second ingress qdisc on an existing interface.
- EXPECT_SOME_FALSE(ingress::create(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(ingress::exists(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
-
- // Remove the ingress qdisc.
- EXPECT_SOME_TRUE(ingress::remove(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
-
- // Try to remove it from a nonexistent interface.
- EXPECT_SOME_FALSE(ingress::remove("noSuchInterface"));
-
- // Remove the ingress qdisc when it does not exist.
- EXPECT_SOME_FALSE(ingress::remove(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
- EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_HTBQdisc)
-{
- // Test for a qdisc on a nonexistent interface should fail.
- EXPECT_SOME_FALSE(htb::exists("noSuchInterface", EGRESS_ROOT));
-
- EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- // This test uses a common handle throughout
- const Handle handle = Handle(1, 0);
-
- // Interface exists but does not have an htb qdisc.
- EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Interfaces without qdisc established no data.
- EXPECT_NONE(htb::statistics(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_NONE(htb::statistics(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create an htb qdisc on a nonexistent interface.
- EXPECT_ERROR(htb::create("noSuchInterface", EGRESS_ROOT, handle));
-
- // Create an htb qdisc on an existing interface.
- EXPECT_SOME_TRUE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
-
- // Interface exists and has an htb qdisc.
- EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Interfaces which exist return at least the core statisitcs.
- Result<hashmap<string, uint64_t>> stats =
- htb::statistics(TEST_VETH_LINK, EGRESS_ROOT);
- ASSERT_SOME(stats);
- EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
- EXPECT_TRUE(stats.get().contains(statistics::BYTES));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
- EXPECT_TRUE(stats.get().contains(statistics::QLEN));
- EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
- EXPECT_TRUE(stats.get().contains(statistics::DROPS));
- EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
- EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
-
- // Interface without htb qdisc returns no data.
- EXPECT_NONE(htb::statistics(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create a second htb qdisc on an existing interface.
- EXPECT_SOME_FALSE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
- EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Remove the htb qdisc.
- EXPECT_SOME_TRUE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to remove it from a nonexistent interface.
- EXPECT_SOME_FALSE(htb::remove("noSuchInterface", EGRESS_ROOT));
-
- // Remove the htb qdisc when it does not exist.
- EXPECT_SOME_FALSE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create an htb qdisc on a nonexistent interface and
- // default handle.
- EXPECT_ERROR(htb::create("noSuchInterface", EGRESS_ROOT, None()));
-
- // Create an htb qdisc on an existing interface.
- EXPECT_SOME_TRUE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, None()));
-
- // Interface exists and has an htb qdisc.
- EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Remove the htb qdisc.
- EXPECT_SOME_TRUE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_FqCodeQdisc)
-{
- // Test for a qdisc on a nonexistent interface should fail.
- EXPECT_SOME_FALSE(fq_codel::exists("noSuchInterface", EGRESS_ROOT));
-
- EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- // This test uses a common handle throughout
- const Handle handle = Handle(1, 0);
-
- // Interface exists but does not have an fq_codel qdisc.
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Interfaces without qdisc established no data.
- EXPECT_NONE(fq_codel::statistics(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_NONE(fq_codel::statistics(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create an fq_codel qdisc on a nonexistent interface.
- EXPECT_ERROR(fq_codel::create("noSuchInterface", EGRESS_ROOT, handle));
-
- // Create an fq_codel qdisc on an existing interface.
- EXPECT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
-
- // Interface exists and has an fq_codel qdisc.
- EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Interfaces which exist return at least the core statisitcs.
- Result<hashmap<string, uint64_t>> stats =
- fq_codel::statistics(TEST_VETH_LINK, EGRESS_ROOT);
- ASSERT_SOME(stats);
- EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
- EXPECT_TRUE(stats.get().contains(statistics::BYTES));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
- EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
- EXPECT_TRUE(stats.get().contains(statistics::QLEN));
- EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
- EXPECT_TRUE(stats.get().contains(statistics::DROPS));
- EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
- EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
-
- // Interface without fq_codel qdisc returns no data.
- EXPECT_NONE(fq_codel::statistics(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create a second fq_codel qdisc on an existing interface.
- EXPECT_SOME_FALSE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
- EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Remove the fq_codel qdisc.
- EXPECT_SOME_TRUE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to remove it from a nonexistent interface.
- EXPECT_SOME_FALSE(fq_codel::remove("noSuchInterface", EGRESS_ROOT));
-
- // Remove the fq_codel qdisc when it does not exist.
- EXPECT_SOME_FALSE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Try to create an fq_codel qdisc on a nonexistent interface and
- // default handle.
- EXPECT_ERROR(fq_codel::create("noSuchInterface", EGRESS_ROOT, None()));
-
- // Create an fq_codel qdisc on an existing interface.
- EXPECT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, None()));
-
- // Interface exists and has an fq_codel qdisc.
- EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-
- // Remove the fq_codel qdisc.
- EXPECT_SOME_TRUE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
- EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_FqCodelClassifier)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- const Handle handle = Handle(1, 0);
- ASSERT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- handle,
- ETH_P_ALL,
- None(),
- Handle(handle, 0)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, handle, ETH_P_ALL));
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- handle,
- ETH_P_ARP,
- None(),
- Handle(handle, 0)));
-
- // There is a kernel bug which could cause this test fail. Please
- // make sure your kernel, if newer than 3.14, has commit:
- // b057df24a7536cce6c372efe9d0e3d1558afedf4
- // (https://git.kernel.org/cgit/linux/kernel/git/davem/net.git).
- // Please fix your kernel if you see this failure.
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, handle, ETH_P_ARP));
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- handle,
- icmp::Classifier(None()),
- None(),
- Handle(handle, 0)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- handle,
- icmp::Classifier(None())));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts =
- ip::PortRange::fromBeginEnd(1024, 1027);
- ASSERT_SOME(sourcePorts);
-
- Try<ip::PortRange> destinationPorts =
- ip::PortRange::fromBeginEnd(2000, 2000);
- ASSERT_SOME(destinationPorts);
-
- ip::Classifier classifier =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts.get(),
- destinationPorts.get());
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- handle,
- classifier,
- None(),
- Handle(handle, 1)));
-
- EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, handle, classifier));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ARPFilterCreate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ARPFilterCreateDuplicated)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- set<string> links;
- links.insert(TEST_PEER_LINK);
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- None(),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
-
- EXPECT_SOME_FALSE(basic::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ARPFilterRemove)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- set<string> links;
- links.insert(TEST_PEER_LINK);
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- None(),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
- EXPECT_SOME_TRUE(basic::remove(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
- EXPECT_SOME_FALSE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ARPFilterUpdate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- set<string> links;
- links.insert(TEST_PEER_LINK);
-
- EXPECT_SOME_FALSE(basic::update(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(basic::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
-
- EXPECT_SOME_TRUE(basic::update(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ETH_P_ARP,
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ICMPFilterCreate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip),
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip)));
-
- Result<vector<icmp::Classifier> > classifiers =
- icmp::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- ASSERT_EQ(1u, classifiers.get().size());
- EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ICMPFilterCreateDuplicated)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- set<string> links;
- links.insert(TEST_PEER_LINK);
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- None(),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-
- EXPECT_SOME_FALSE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- None(),
- action::Mirror(links)));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ICMPFilterCreateMultiple)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- net::IP ip1 = net::IP(0x01020304); // 1.2.3.4
- net::IP ip2 = net::IP(0x05060708); // 5.6.7.8
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip1),
- Priority(1, 1),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip2),
- Priority(1, 2),
- action::Redirect(TEST_PEER_LINK)));
-
- Result<vector<icmp::Classifier> > classifiers =
- icmp::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- ASSERT_EQ(2u, classifiers.get().size());
- EXPECT_SOME_EQ(ip1, classifiers.get().front().destinationIP);
- EXPECT_SOME_EQ(ip2, classifiers.get().back().destinationIP);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ICMPFilterRemove)
-{
- ASSERT_SOME(link::create(
- TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-
- EXPECT_SOME_TRUE(icmp::remove(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-
- EXPECT_SOME_FALSE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_ICMPFilterUpdate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- set<string> links;
- links.insert(TEST_PEER_LINK);
-
- EXPECT_SOME_FALSE(icmp::update(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(icmp::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-
- EXPECT_SOME_FALSE(icmp::update(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(icmp::update(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None()),
- action::Mirror(links)));
-
- EXPECT_SOME_TRUE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(None())));
-
- EXPECT_SOME_FALSE(icmp::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- icmp::Classifier(ip)));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IPFilterCreate)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts =
- ip::PortRange::fromBeginEnd(1024, 1027);
-
- ASSERT_SOME(sourcePorts);
-
- Try<ip::PortRange> destinationPorts =
- ip::PortRange::fromBeginEnd(2000, 2000);
-
- ASSERT_SOME(destinationPorts);
-
- ip::Classifier classifier =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts.get(),
- destinationPorts.get());
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier));
-
- Result<vector<ip::Classifier> > classifiers =
- ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- ASSERT_EQ(1u, classifiers.get().size());
- EXPECT_SOME_EQ(mac.get(), classifiers.get().front().destinationMAC);
- EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
-
- EXPECT_SOME_EQ(
- sourcePorts.get(),
- classifiers.get().front().sourcePorts);
-
- EXPECT_SOME_EQ(
- destinationPorts.get(),
- classifiers.get().front().destinationPorts);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IPFilterCreate2)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- net::IP ip(0x12345678);
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ip::Classifier(None(), ip, None(), None()),
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::exists(
- TEST_VETH_LINK,
- ingress::HANDLE,
- ip::Classifier(None(), ip, None(), None())));
-
- Result<vector<ip::Classifier> > classifiers =
- ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- ASSERT_EQ(1u, classifiers.get().size());
- EXPECT_NONE(classifiers.get().front().destinationMAC);
- EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
- EXPECT_NONE(classifiers.get().front().sourcePorts);
- EXPECT_NONE(classifiers.get().front().destinationPorts);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IPFilterCreateDuplicated)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts =
- ip::PortRange::fromBeginEnd(1024, 1027);
-
- ASSERT_SOME(sourcePorts);
-
- Try<ip::PortRange> destinationPorts =
- ip::PortRange::fromBeginEnd(2000, 2000);
-
- ASSERT_SOME(destinationPorts);
-
- ip::Classifier classifier =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts.get(),
- destinationPorts.get());
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier));
-
- EXPECT_SOME_FALSE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IPFilterCreateMultiple)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts1 =
- ip::PortRange::fromBeginEnd(1024, 1027);
-
- ASSERT_SOME(sourcePorts1);
-
- Try<ip::PortRange> destinationPorts1 =
- ip::PortRange::fromBeginEnd(2000, 2000);
-
- ASSERT_SOME(destinationPorts1);
-
- Try<ip::PortRange> sourcePorts2 =
- ip::PortRange::fromBeginEnd(3024, 3025);
-
- ASSERT_SOME(sourcePorts2);
-
- Try<ip::PortRange> destinationPorts2 =
- ip::PortRange::fromBeginEnd(4000, 4003);
-
- ASSERT_SOME(destinationPorts2);
-
- ip::Classifier classifier1 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts1.get(),
- destinationPorts1.get());
-
- ip::Classifier classifier2 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts2.get(),
- destinationPorts2.get());
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier1,
- Priority(2, 1),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier2,
- Priority(2, 2),
- action::Redirect(TEST_PEER_LINK)));
-
- Result<vector<ip::Classifier> > classifiers =
- ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- ASSERT_EQ(2u, classifiers.get().size());
-
- EXPECT_SOME_EQ(mac.get(), classifiers.get().front().destinationMAC);
- EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
-
- EXPECT_SOME_EQ(
- sourcePorts1.get(),
- classifiers.get().front().sourcePorts);
-
- EXPECT_SOME_EQ(
- destinationPorts1.get(),
- classifiers.get().front().destinationPorts);
-
- EXPECT_SOME_EQ(mac.get(), classifiers.get().back().destinationMAC);
- EXPECT_SOME_EQ(ip, classifiers.get().back().destinationIP);
-
- EXPECT_SOME_EQ(
- sourcePorts2.get(),
- classifiers.get().back().sourcePorts);
-
- EXPECT_SOME_EQ(
- destinationPorts2.get(),
- classifiers.get().back().destinationPorts);
-}
-
-
-TEST_F(RoutingVethTest, ROOT_IPFilterRemove)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts1 =
- ip::PortRange::fromBeginEnd(1024, 1027);
-
- ASSERT_SOME(sourcePorts1);
-
- Try<ip::PortRange> destinationPorts1 =
- ip::PortRange::fromBeginEnd(2000, 2000);
-
- ASSERT_SOME(destinationPorts1);
-
- Try<ip::PortRange> sourcePorts2 =
- ip::PortRange::fromBeginEnd(3024, 3025);
-
- ASSERT_SOME(sourcePorts2);
-
- Try<ip::PortRange> destinationPorts2 =
- ip::PortRange::fromBeginEnd(4000, 4003);
-
- ASSERT_SOME(destinationPorts2);
-
- ip::Classifier classifier1 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts1.get(),
- destinationPorts1.get());
-
- ip::Classifier classifier2 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts2.get(),
- destinationPorts2.get());
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier1,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier2,
- None(),
- action::Redirect(TEST_PEER_LINK)));
-
- EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier1));
- EXPECT_SOME_FALSE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier1));
-
- EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier2));
- EXPECT_SOME_FALSE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier2));
-
- Result<vector<ip::Classifier> > classifiers =
- ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
-
- ASSERT_SOME(classifiers);
- EXPECT_EQ(0u, classifiers.get().size());
-}
-
-
-// Test the workaround introduced for MESOS-1617.
-TEST_F(RoutingVethTest, ROOT_HandleGeneration)
-{
- ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
-
- EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
- EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
-
- ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
-
- Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
- ASSERT_SOME(mac);
-
- net::IP ip = net::IP(0x01020304); // 1.2.3.4
-
- Try<ip::PortRange> sourcePorts1 =
- ip::PortRange::fromBeginEnd(1024, 1027);
-
- ASSERT_SOME(sourcePorts1);
-
- Try<ip::PortRange> destinationPorts1 =
- ip::PortRange::fromBeginEnd(2000, 2000);
-
- ASSERT_SOME(destinationPorts1);
-
- Try<ip::PortRange> sourcePorts2 =
- ip::PortRange::fromBeginEnd(3024, 3025);
-
- ASSERT_SOME(sourcePorts2);
-
- Try<ip::PortRange> destinationPorts2 =
- ip::PortRange::fromBeginEnd(4000, 4003);
-
- ASSERT_SOME(destinationPorts2);
-
- ip::Classifier classifier1 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts1.get(),
- destinationPorts1.get());
-
- ip::Classifier classifier2 =
- ip::Classifier(
- mac.get(),
- ip,
- sourcePorts2.get(),
- destinationPorts2.get());
-
- // Use handle 800:00:fff for the first filter.
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier1,
- Priority(2, 1),
- U32Handle(0x800, 0x0, 0xfff),
- action::Redirect(TEST_PEER_LINK)));
-
- // With the workaround, this filter should be assigned a handle
- // different than 800:00:fff.
- EXPECT_SOME_TRUE(ip::create(
- TEST_VETH_LINK,
- ingress::HANDLE,
- classifier2,
- Priority(2, 1),
- action::Redirect(TEST_PEER_LINK)));
-
- // Try to remove the second filter. If we don't have the workaround,
- // removing the second filter will return false since the kernel
- // will find the handle matches the first filter.
- EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier2));
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/sched_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/sched_tests.cpp b/src/tests/sched_tests.cpp
deleted file mode 100644
index 00723d0..0000000
--- a/src/tests/sched_tests.cpp
+++ /dev/null
@@ -1,96 +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 "linux/sched.hpp"
-
-#include <process/gtest.hpp>
-#include <process/reap.hpp>
-
-#include <gtest/gtest.h>
-
-#include <stout/gtest.hpp>
-
-using sched::Policy;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-// TODO(idownes): Test the priority and preemption behavior for
-// running competing SCHED_OTHER and SCHED_IDLE tasks.
-
-TEST(SchedTest, ROOT_PolicySelf)
-{
- Try<Policy> original = sched::policy::get();
- ASSERT_SOME(original);
-
- Policy different = (original.get() == Policy::OTHER ? Policy::IDLE
- : Policy::OTHER);
-
- // Change our own scheduling policy.
- EXPECT_SOME(sched::policy::set(different));
- EXPECT_SOME_EQ(different, sched::policy::get());
-
- // Change it back.
- EXPECT_SOME(sched::policy::set(original.get()));
- EXPECT_SOME_EQ(original.get(), sched::policy::get());
-}
-
-
-// Change the scheduling policy of a different process (our child).
-TEST(SchedTest, ROOT_PolicyChild)
-{
- Try<Policy> original = sched::policy::get();
- ASSERT_SOME(original);
-
- Policy different = (original.get() == Policy::OTHER ? Policy::IDLE
- : Policy::OTHER);
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // Child.
- sleep(10);
-
- ABORT("Child process should not reach here");
- }
-
- // Continue in parent.
- // Check the child has inherited our policy.
- EXPECT_SOME_EQ(original.get(), sched::policy::get(pid));
-
- // Check we can change the child's policy.
- EXPECT_SOME(sched::policy::set(different, pid));
- EXPECT_SOME_EQ(different, sched::policy::get(pid));
-
- process::Future<Option<int>> status = process::reap(pid);
-
- // Kill the child process.
- ASSERT_NE(-1, ::kill(pid, SIGKILL));
-
- // Wait for the child process.
- AWAIT_READY(status);
- ASSERT_SOME(status.get());
- EXPECT_TRUE(WIFSIGNALED(status.get().get()));
- EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/setns_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/setns_test_helper.cpp b/src/tests/setns_test_helper.cpp
deleted file mode 100644
index eff1e6e..0000000
--- a/src/tests/setns_test_helper.cpp
+++ /dev/null
@@ -1,63 +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 <stout/foreach.hpp>
-#include <stout/subcommand.hpp>
-#include <stout/try.hpp>
-
-#include "linux/ns.hpp"
-
-#include "tests/setns_test_helper.hpp"
-
-#include <set>
-#include <string>
-
-using std::set;
-using std::string;
-
-const char SetnsTestHelper::NAME[] = "SetnsTestHelper";
-
-int SetnsTestHelper::execute()
-{
- // Get all the available namespaces.
- set<string> namespaces = ns::namespaces();
-
- // Note: /proc has not been remounted so we can look up pid 1's
- // namespaces, even if we're in a separate pid namespace.
- foreach (const string& ns, namespaces) {
- if (ns == "pid") {
- // ns::setns() does not (currently) support pid namespaces so
- // this should return an error.
- Try<Nothing> setns = ns::setns(1, ns);
- if (!setns.isError()) {
- return 1;
- }
- } else if (ns == "user") {
- // ns::setns() will also fail with user namespaces, so we skip
- // for now. See MESOS-3083.
- continue;
- } else {
- Try<Nothing> setns = ns::setns(1, ns);
- if (!setns.isSome()) {
- return 1;
- }
- }
- }
-
- return 0;
-}
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/setns_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/setns_test_helper.hpp b/src/tests/setns_test_helper.hpp
deleted file mode 100644
index 51d6378..0000000
--- a/src/tests/setns_test_helper.hpp
+++ /dev/null
@@ -1,35 +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.
- */
-
-#ifndef __SETNS_TEST_HELPER_HPP__
-#define __SETNS_TEST_HELPER_HPP__
-
-#include <stout/subcommand.hpp>
-
-class SetnsTestHelper : public Subcommand
-{
-public:
- static const char NAME[];
-
- SetnsTestHelper() : Subcommand(NAME) {}
-
-protected:
- virtual int execute();
-};
-
-#endif // __SETNS_TEST_HELPER_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/setns_test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/setns_test_helper_main.cpp b/src/tests/setns_test_helper_main.cpp
deleted file mode 100644
index 00d3816..0000000
--- a/src/tests/setns_test_helper_main.cpp
+++ /dev/null
@@ -1,30 +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 <stout/subcommand.hpp>
-
-#include "tests/setns_test_helper.hpp"
-
-int main(int argc, char** argv)
-{
- return Subcommand::dispatch(
- None(),
- argc,
- argv,
- new SetnsTestHelper());
-}
[04/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/docker_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/docker_tests.cpp b/src/tests/docker_tests.cpp
deleted file mode 100644
index a4a2725..0000000
--- a/src/tests/docker_tests.cpp
+++ /dev/null
@@ -1,421 +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 <gtest/gtest.h>
-
-#include <process/future.hpp>
-#include <process/gtest.hpp>
-#include <process/owned.hpp>
-#include <process/subprocess.hpp>
-
-#include <stout/duration.hpp>
-#include <stout/option.hpp>
-#include <stout/gtest.hpp>
-
-#include "docker/docker.hpp"
-
-#include "mesos/resources.hpp"
-
-#include "tests/environment.hpp"
-#include "tests/flags.hpp"
-#include "tests/mesos.hpp"
-
-using namespace process;
-
-using std::list;
-using std::string;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-static const string NAME_PREFIX="mesos-docker";
-
-
-class DockerTest : public MesosTest
-{
- virtual void TearDown()
- {
- Try<Docker*> docker = Docker::create(tests::flags.docker, false);
- ASSERT_SOME(docker);
-
- Future<list<Docker::Container>> containers =
- docker.get()->ps(true, NAME_PREFIX);
-
- AWAIT_READY(containers);
-
- // Cleanup all mesos launched containers.
- foreach (const Docker::Container& container, containers.get()) {
- AWAIT_READY_FOR(docker.get()->rm(container.id, true), Seconds(30));
- }
-
- delete docker.get();
- }
-};
-
-// This test tests the functionality of the docker's interfaces.
-TEST_F(DockerTest, ROOT_DOCKER_interface)
-{
- const string containerName = NAME_PREFIX + "-test";
- Resources resources = Resources::parse("cpus:1;mem:512").get();
-
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- // Verify that we do not see the container.
- Future<list<Docker::Container> > containers = docker->ps(true, containerName);
- AWAIT_READY(containers);
- foreach (const Docker::Container& container, containers.get()) {
- EXPECT_NE("/" + containerName, container.name);
- }
-
- Try<string> directory = environment->mkdtemp();
- CHECK_SOME(directory) << "Failed to create temporary directory";
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_value("sleep 120");
-
- // Start the container.
- Future<Nothing> status = docker->run(
- containerInfo,
- commandInfo,
- containerName,
- directory.get(),
- "/mnt/mesos/sandbox",
- resources);
-
- Future<Docker::Container> inspect =
- docker->inspect(containerName, Seconds(1));
- AWAIT_READY(inspect);
-
- // Should be able to see the container now.
- containers = docker->ps();
- AWAIT_READY(containers);
- bool found = false;
- foreach (const Docker::Container& container, containers.get()) {
- if ("/" + containerName == container.name) {
- found = true;
- break;
- }
- }
- EXPECT_TRUE(found);
-
- // Test some fields of the container.
- EXPECT_NE("", inspect.get().id);
- EXPECT_EQ("/" + containerName, inspect.get().name);
- EXPECT_SOME(inspect.get().pid);
-
- // Stop the container.
- status = docker->stop(containerName);
- AWAIT_READY(status);
-
- // Now, the container should not appear in the result of ps().
- // But it should appear in the result of ps(true).
- containers = docker->ps();
- AWAIT_READY(containers);
- foreach (const Docker::Container& container, containers.get()) {
- EXPECT_NE("/" + containerName, container.name);
- }
-
- containers = docker->ps(true, containerName);
- AWAIT_READY(containers);
- found = false;
- foreach (const Docker::Container& container, containers.get()) {
- if ("/" + containerName == container.name) {
- found = true;
- break;
- }
- }
- EXPECT_TRUE(found);
-
- // Check the container's info, both id and name should remain the
- // same since we haven't removed it, but the pid should be none
- // since it's not running.
- inspect = docker->inspect(containerName);
- AWAIT_READY(inspect);
-
- EXPECT_NE("", inspect.get().id);
- EXPECT_EQ("/" + containerName, inspect.get().name);
- EXPECT_NONE(inspect.get().pid);
-
- // Remove the container.
- status = docker->rm(containerName);
- AWAIT_READY(status);
-
- // Should not be able to inspect the container.
- inspect = docker->inspect(containerName);
- AWAIT_FAILED(inspect);
-
- // Also, now we should not be able to see the container by invoking
- // ps(true).
- containers = docker->ps(true, containerName);
- AWAIT_READY(containers);
- foreach (const Docker::Container& container, containers.get()) {
- EXPECT_NE("/" + containerName, container.name);
- }
-
- // Start the container again, this time we will do a "rm -f"
- // directly, instead of stopping and rm.
- status = docker->run(
- containerInfo,
- commandInfo,
- containerName,
- directory.get(),
- "/mnt/mesos/sandbox",
- resources);
-
- inspect = docker->inspect(containerName, Seconds(1));
- AWAIT_READY(inspect);
-
- // Verify that the container is there.
- containers = docker->ps();
- AWAIT_READY(containers);
- found = false;
- foreach (const Docker::Container& container, containers.get()) {
- if ("/" + containerName == container.name) {
- found = true;
- break;
- }
- }
- EXPECT_TRUE(found);
-
- // Then do a "rm -f".
- status = docker->rm(containerName, true);
- AWAIT_READY(status);
-
- // Verify that the container is totally removed, that is we can't
- // find it by ps() or ps(true).
- containers = docker->ps();
- AWAIT_READY(containers);
- foreach (const Docker::Container& container, containers.get()) {
- EXPECT_NE("/" + containerName, container.name);
- }
- containers = docker->ps(true, containerName);
- AWAIT_READY(containers);
- foreach (const Docker::Container& container, containers.get()) {
- EXPECT_NE("/" + containerName, container.name);
- }
-}
-
-
-TEST_F(DockerTest, ROOT_DOCKER_CheckCommandWithShell)
-{
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_shell(true);
-
- Future<Nothing> run = docker->run(
- containerInfo,
- commandInfo,
- "testContainer",
- "dir",
- "/mnt/mesos/sandbox");
-
- ASSERT_TRUE(run.isFailed());
-}
-
-
-TEST_F(DockerTest, ROOT_DOCKER_CheckPortResource)
-{
- const string containerName = NAME_PREFIX + "-port-resource-test";
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- // Make sure the container is removed.
- Future<Nothing> remove = docker->rm(containerName, true);
-
- ASSERT_TRUE(process::internal::await(remove, Seconds(10)));
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
-
- ContainerInfo::DockerInfo::PortMapping portMapping;
- portMapping.set_host_port(10000);
- portMapping.set_container_port(80);
-
- dockerInfo.add_port_mappings()->CopyFrom(portMapping);
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_shell(false);
- commandInfo.set_value("true");
-
- Resources resources =
- Resources::parse("ports:[9998-9999];ports:[10001-11000]").get();
-
- Future<Nothing> run = docker->run(
- containerInfo,
- commandInfo,
- containerName,
- "dir",
- "/mnt/mesos/sandbox",
- resources);
-
- // Port should be out side of the provided ranges.
- AWAIT_EXPECT_FAILED(run);
-
- resources = Resources::parse("ports:[9998-9999];ports:[10000-11000]").get();
-
- Try<string> directory = environment->mkdtemp();
- CHECK_SOME(directory) << "Failed to create temporary directory";
-
- run = docker->run(
- containerInfo,
- commandInfo,
- containerName,
- directory.get(),
- "/mnt/mesos/sandbox",
- resources);
-
- AWAIT_READY(run);
-}
-
-
-TEST_F(DockerTest, ROOT_DOCKER_CancelPull)
-{
- // Delete the test image if it exists.
-
- Try<Subprocess> s = process::subprocess(
- tests::flags.docker + " rmi lingmann/1gb",
- Subprocess::PATH("/dev/null"),
- Subprocess::PATH("/dev/null"),
- Subprocess::PATH("/dev/null"));
-
- ASSERT_SOME(s);
-
- AWAIT_READY_FOR(s.get().status(), Seconds(30));
-
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- Try<string> directory = environment->mkdtemp();
-
- CHECK_SOME(directory) << "Failed to create temporary directory";
-
- // Assume that pulling the very large image 'lingmann/1gb' will take
- // sufficiently long that we can start it and discard (i.e., cancel
- // it) right away and the future will indeed get discarded.
- Future<Docker::Image> future =
- docker->pull(directory.get(), "lingmann/1gb");
-
- future.discard();
-
- AWAIT_DISCARDED(future);
-}
-
-
-// This test verifies mounting in a relative path when running a
-// docker container works.
-TEST_F(DockerTest, ROOT_DOCKER_MountRelative)
-{
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- Volume* volume = containerInfo.add_volumes();
- volume->set_host_path("test_file");
- volume->set_container_path("/tmp/test_file");
- volume->set_mode(Volume::RO);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_shell(true);
- commandInfo.set_value("ls /tmp/test_file");
-
- Try<string> directory = environment->mkdtemp();
- CHECK_SOME(directory) << "Failed to create temporary directory";
-
- const string testFile = path::join(directory.get(), "test_file");
- EXPECT_SOME(os::write(testFile, "data"));
-
- Future<Nothing> run = docker->run(
- containerInfo,
- commandInfo,
- NAME_PREFIX + "-mount-relative-test",
- directory.get(),
- directory.get());
-
- AWAIT_READY(run);
-}
-
-
-// This test verifies mounting in a absolute path when running a
-// docker container works.
-TEST_F(DockerTest, ROOT_DOCKER_MountAbsolute)
-{
- Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- Try<string> directory = environment->mkdtemp();
- CHECK_SOME(directory) << "Failed to create temporary directory";
-
- const string testFile = path::join(directory.get(), "test_file");
- EXPECT_SOME(os::write(testFile, "data"));
-
- Volume* volume = containerInfo.add_volumes();
- volume->set_host_path(testFile);
- volume->set_container_path("/tmp/test_file");
- volume->set_mode(Volume::RO);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_shell(true);
- commandInfo.set_value("ls /tmp/test_file");
-
- Future<Nothing> run = docker->run(
- containerInfo,
- commandInfo,
- NAME_PREFIX + "-mount-absolute-test",
- directory.get(),
- directory.get());
-
- AWAIT_READY(run);
-}
-
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/external_containerizer_test.cpp
----------------------------------------------------------------------
diff --git a/src/tests/external_containerizer_test.cpp b/src/tests/external_containerizer_test.cpp
deleted file mode 100644
index 17bfb72..0000000
--- a/src/tests/external_containerizer_test.cpp
+++ /dev/null
@@ -1,266 +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 <unistd.h>
-
-#include <gmock/gmock.h>
-
-#include <string>
-#include <vector>
-#include <map>
-
-#include <mesos/resources.hpp>
-
-#include <process/future.hpp>
-
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-
-#include "master/master.hpp"
-#include "master/detector.hpp"
-
-#include "slave/containerizer/containerizer.hpp"
-#include "slave/containerizer/external_containerizer.hpp"
-#include "slave/flags.hpp"
-#include "slave/slave.hpp"
-
-#include "tests/mesos.hpp"
-#include "tests/flags.hpp"
-
-using namespace process;
-
-using mesos::internal::master::Master;
-using mesos::internal::slave::Containerizer;
-using mesos::internal::slave::Slave;
-
-using std::string;
-using std::vector;
-
-using testing::_;
-using testing::DoAll;
-using testing::Return;
-using testing::SaveArg;
-using testing::Invoke;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-// The external containerizer tests currently rely on a Python script
-// which needs the Mesos Python egg being built.
-// TODO(tillt): Consider providing tests that do not rely on Python.
-#ifdef MESOS_HAS_PYTHON
-
-// TODO(tillt): Update and enhance the ExternalContainerizer tests,
-// possibly following some of the patterns used within the
-// IsolatorTests or even entirely reusing the Containerizer tests.
-class ExternalContainerizerTest : public MesosTest {};
-
-
-class MockExternalContainerizer : public slave::ExternalContainerizer
-{
-public:
- MOCK_METHOD8(
- launch,
- process::Future<bool>(
- const ContainerID&,
- const TaskInfo&,
- const ExecutorInfo&,
- const std::string&,
- const Option<std::string>&,
- const SlaveID&,
- const process::PID<slave::Slave>&,
- bool checkpoint));
-
- MockExternalContainerizer(const slave::Flags& flags)
- : ExternalContainerizer(flags)
- {
- // Set up defaults for mocked methods.
- // NOTE: See TestContainerizer::setup for why we use
- // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
- // 'ON_CALL' and 'WillByDefault'.
- EXPECT_CALL(*this, launch(_, _, _, _, _, _, _, _))
- .WillRepeatedly(Invoke(this, &MockExternalContainerizer::_launch));
- }
-
- process::Future<bool> _launch(
- const ContainerID& containerId,
- const TaskInfo& taskInfo,
- const ExecutorInfo& executorInfo,
- const string& directory,
- const Option<string>& user,
- const SlaveID& slaveId,
- const PID<Slave>& slavePid,
- bool checkpoint)
- {
- return slave::ExternalContainerizer::launch(
- containerId,
- taskInfo,
- executorInfo,
- directory,
- user,
- slaveId,
- slavePid,
- checkpoint);
- }
-};
-
-
-// This test has been temporarily disabled due to MESOS-1257.
-TEST_F(ExternalContainerizerTest, DISABLED_Launch)
-{
- Try<PID<Master> > master = this->StartMaster();
- ASSERT_SOME(master);
-
- Flags testFlags;
-
- slave::Flags flags = this->CreateSlaveFlags();
-
- flags.isolation = "external";
- flags.containerizer_path =
- testFlags.build_dir + "/src/examples/python/test-containerizer";
-
- MockExternalContainerizer containerizer(flags);
-
- Try<PID<Slave> > slave = this->StartSlave(&containerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
- AWAIT_READY(offers);
-
- EXPECT_NE(0u, offers.get().size());
-
- TaskInfo task;
- task.set_name("isolator_test");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id());
- task.mutable_resources()->CopyFrom(offers.get()[0].resources());
-
- Resources resources(offers.get()[0].resources());
- Option<Bytes> mem = resources.mem();
- ASSERT_SOME(mem);
- Option<double> cpus = resources.cpus();
- ASSERT_SOME(cpus);
-
- const std::string& file = path::join(flags.work_dir, "ready");
-
- // This task induces user/system load in a child process by
- // running top in a child process for ten seconds.
- task.mutable_command()->set_value(
-#ifdef __APPLE__
- // Use logging mode with 30,000 samples with no interval.
- "top -l 30000 -s 0 2>&1 > /dev/null & "
-#else
- // Batch mode, with 30,000 samples with no interval.
- "top -b -d 0 -n 30000 2>&1 > /dev/null & "
-#endif
- "touch " + file + "; " // Signals that the top command is running.
- "sleep 60");
-
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status))
- .WillRepeatedly(Return()); // Ignore rest for now.
-
- Future<ContainerID> containerId;
- EXPECT_CALL(containerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&containerizer,
- &MockExternalContainerizer::_launch)));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(containerId);
-
- AWAIT_READY(status);
-
- EXPECT_EQ(TASK_RUNNING, status.get().state());
-
- // Wait for the task to begin inducing cpu time.
- while (!os::exists(file));
-
- ExecutorID executorId;
- executorId.set_value(task.task_id().value());
-
- // We'll wait up to 10 seconds for the child process to induce
- // 1/8 of a second of user and system cpu time in total.
- // TODO(bmahler): Also induce rss memory consumption, by re-using
- // the balloon framework.
- ResourceStatistics statistics;
- Duration waited = Duration::zero();
- do {
- Future<ResourceStatistics> usage = containerizer.usage(containerId.get());
- AWAIT_READY(usage);
-
- statistics = usage.get();
-
- // If we meet our usage expectations, we're done!
- // NOTE: We are currently getting dummy-data from the test-
- // containerizer python script matching these expectations.
- // TODO(tillt): Consider working with real data.
- if (statistics.cpus_user_time_secs() >= 0.120 &&
- statistics.cpus_system_time_secs() >= 0.05 &&
- statistics.mem_rss_bytes() >= 1024u) {
- break;
- }
-
- os::sleep(Milliseconds(100));
- waited += Milliseconds(100);
- } while (waited < Seconds(10));
-
- EXPECT_GE(statistics.cpus_user_time_secs(), 0.120);
- EXPECT_GE(statistics.cpus_system_time_secs(), 0.05);
- EXPECT_EQ(statistics.cpus_limit(), cpus.get());
- EXPECT_GE(statistics.mem_rss_bytes(), 1024u);
- EXPECT_EQ(statistics.mem_limit_bytes(), mem.get().bytes());
-
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status));
-
- driver.killTask(task.task_id());
-
- AWAIT_READY(status);
-
- EXPECT_EQ(TASK_KILLED, status.get().state());
-
- driver.stop();
- driver.join();
-
- this->Shutdown();
-}
-
-#endif // MESOS_HAS_PYTHON
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/fs_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/fs_tests.cpp b/src/tests/fs_tests.cpp
deleted file mode 100644
index 34d3c41..0000000
--- a/src/tests/fs_tests.cpp
+++ /dev/null
@@ -1,170 +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 <paths.h>
-
-#include <gmock/gmock.h>
-
-#include <stout/foreach.hpp>
-#include <stout/gtest.hpp>
-#include <stout/none.hpp>
-#include <stout/option.hpp>
-#include <stout/try.hpp>
-
-#include "linux/fs.hpp"
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-using fs::MountTable;
-using fs::FileSystemTable;
-using fs::MountInfoTable;
-
-
-TEST(FsTest, MountTableRead)
-{
- Try<MountTable> table = MountTable::read(_PATH_MOUNTED);
-
- ASSERT_SOME(table);
-
- Option<MountTable::Entry> root = None();
- Option<MountTable::Entry> proc = None();
- foreach (const MountTable::Entry& entry, table.get().entries) {
- if (entry.dir == "/") {
- root = entry;
- } else if (entry.dir == "/proc") {
- proc = entry;
- }
- }
-
- EXPECT_SOME(root);
- ASSERT_SOME(proc);
- EXPECT_EQ(proc.get().type, "proc");
-}
-
-
-TEST(FsTest, MountTableHasOption)
-{
- Try<MountTable> table = MountTable::read(_PATH_MOUNTED);
-
- ASSERT_SOME(table);
-
- Option<MountTable::Entry> proc = None();
- foreach (const MountTable::Entry& entry, table.get().entries) {
- if (entry.dir == "/proc") {
- proc = entry;
- }
- }
-
- ASSERT_SOME(proc);
- EXPECT_TRUE(proc.get().hasOption(MNTOPT_RW));
-}
-
-
-TEST(FsTest, FileSystemTableRead)
-{
- Try<FileSystemTable> table = FileSystemTable::read();
-
- ASSERT_SOME(table);
-
- // NOTE: We do not check for /proc because, it is not always present in
- // /etc/fstab.
- Option<FileSystemTable::Entry> root = None();
- foreach (const FileSystemTable::Entry& entry, table.get().entries) {
- if (entry.file == "/") {
- root = entry;
- }
- }
-
- EXPECT_SOME(root);
-}
-
-
-TEST(FsTest, MountInfoTableParse)
-{
- // Parse a private mount (no optional fields).
- const std::string privateMount =
- "19 1 8:1 / / rw,relatime - ext4 /dev/sda1 rw,seclabel,data=ordered";
- Try<MountInfoTable::Entry> entry = MountInfoTable::Entry::parse(privateMount);
-
- ASSERT_SOME(entry);
- EXPECT_EQ(19, entry.get().id);
- EXPECT_EQ(1, entry.get().parent);
- EXPECT_EQ(makedev(8, 1), entry.get().devno);
- EXPECT_EQ("/", entry.get().root);
- EXPECT_EQ("/", entry.get().target);
- EXPECT_EQ("rw,relatime", entry.get().vfsOptions);
- EXPECT_EQ("rw,seclabel,data=ordered", entry.get().fsOptions);
- EXPECT_EQ("", entry.get().optionalFields);
- EXPECT_EQ("ext4", entry.get().type);
- EXPECT_EQ("/dev/sda1", entry.get().source);
-
- // Parse a shared mount (includes one optional field).
- const std::string sharedMount =
- "19 1 8:1 / / rw,relatime shared:2 - ext4 /dev/sda1 rw,seclabel";
- entry = MountInfoTable::Entry::parse(sharedMount);
-
- ASSERT_SOME(entry);
- EXPECT_EQ(19, entry.get().id);
- EXPECT_EQ(1, entry.get().parent);
- EXPECT_EQ(makedev(8, 1), entry.get().devno);
- EXPECT_EQ("/", entry.get().root);
- EXPECT_EQ("/", entry.get().target);
- EXPECT_EQ("rw,relatime", entry.get().vfsOptions);
- EXPECT_EQ("rw,seclabel", entry.get().fsOptions);
- EXPECT_EQ("shared:2", entry.get().optionalFields);
- EXPECT_EQ("ext4", entry.get().type);
- EXPECT_EQ("/dev/sda1", entry.get().source);
-}
-
-
-TEST(FsTest, DISABLED_MountInfoTableRead)
-{
- // Examine the calling process's mountinfo table.
- Try<fs::MountInfoTable> table = fs::MountInfoTable::read();
- ASSERT_SOME(table);
-
- // Every system should have at least a rootfs mounted.
- Option<MountInfoTable::Entry> root = None();
- foreach (const MountInfoTable::Entry& entry, table.get().entries) {
- if (entry.target == "/") {
- root = entry;
- }
- }
-
- EXPECT_SOME(root);
-
- // Repeat for pid 1.
- table = fs::MountInfoTable::read(1);
- ASSERT_SOME(table);
-
- // Every system should have at least a rootfs mounted.
- root = None();
- foreach (const MountInfoTable::Entry& entry, table.get().entries) {
- if (entry.target == "/") {
- root = entry;
- }
- }
-
- EXPECT_SOME(root);
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/isolator.hpp
----------------------------------------------------------------------
diff --git a/src/tests/isolator.hpp b/src/tests/isolator.hpp
deleted file mode 100644
index 8aaf88c..0000000
--- a/src/tests/isolator.hpp
+++ /dev/null
@@ -1,101 +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.
- */
-
-#ifndef __TEST_ISOLATOR_HPP__
-#define __TEST_ISOLATOR_HPP__
-
-#include <gmock/gmock.h>
-
-#include "slave/containerizer/isolator.hpp"
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class TestIsolatorProcess : public slave::MesosIsolatorProcess
-{
-public:
- static Try<mesos::slave::Isolator*> create(
- const Option<CommandInfo>& commandInfo)
- {
- process::Owned<MesosIsolatorProcess> process(
- new TestIsolatorProcess(commandInfo));
-
- return new slave::MesosIsolator(process);
- }
-
- MOCK_METHOD2(
- recover,
- process::Future<Nothing>(
- const std::list<mesos::slave::ExecutorRunState>&,
- const hashset<ContainerID>&));
-
- virtual process::Future<Option<CommandInfo>> prepare(
- const ContainerID& containerId,
- const ExecutorInfo& executorInfo,
- const std::string& directory,
- const Option<std::string>& rootfs,
- const Option<std::string>& user)
- {
- return commandInfo;
- }
-
- MOCK_METHOD2(
- isolate,
- process::Future<Nothing>(const ContainerID&, pid_t));
-
- MOCK_METHOD1(
- watch,
- process::Future<mesos::slave::ExecutorLimitation>(const ContainerID&));
-
- MOCK_METHOD2(
- update,
- process::Future<Nothing>(const ContainerID&, const Resources&));
-
- MOCK_METHOD1(
- usage,
- process::Future<ResourceStatistics>(const ContainerID&));
-
- MOCK_METHOD1(
- cleanup,
- process::Future<Nothing>(const ContainerID&));
-
-private:
- TestIsolatorProcess(const Option<CommandInfo>& _commandInfo)
- : commandInfo(_commandInfo)
- {
- EXPECT_CALL(*this, watch(testing::_))
- .WillRepeatedly(testing::Return(promise.future()));
-
- EXPECT_CALL(*this, isolate(testing::_, testing::_))
- .WillRepeatedly(testing::Return(Nothing()));
-
- EXPECT_CALL(*this, cleanup(testing::_))
- .WillRepeatedly(testing::Return(Nothing()));
- }
-
- const Option<CommandInfo> commandInfo;
-
- process::Promise<mesos::slave::ExecutorLimitation> promise;
-};
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __TEST_ISOLATOR_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/isolator_tests.cpp b/src/tests/isolator_tests.cpp
deleted file mode 100644
index 7ad0cb6..0000000
--- a/src/tests/isolator_tests.cpp
+++ /dev/null
@@ -1,1316 +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 <unistd.h>
-
-#include <gmock/gmock.h>
-
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include <mesos/resources.hpp>
-
-#include <mesos/module/isolator.hpp>
-
-#include <mesos/slave/isolator.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-#include <process/reap.hpp>
-
-#include <stout/abort.hpp>
-#include <stout/gtest.hpp>
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-
-#ifdef __linux__
-#include "linux/ns.hpp"
-#endif // __linux__
-
-#include "master/master.hpp"
-#include "master/detector.hpp"
-
-#include "slave/flags.hpp"
-#include "slave/slave.hpp"
-
-#ifdef __linux__
-#include "slave/containerizer/isolators/cgroups/constants.hpp"
-#include "slave/containerizer/isolators/cgroups/cpushare.hpp"
-#include "slave/containerizer/isolators/cgroups/mem.hpp"
-#include "slave/containerizer/isolators/cgroups/perf_event.hpp"
-#include "slave/containerizer/isolators/filesystem/shared.hpp"
-#endif // __linux__
-#include "slave/containerizer/isolators/posix.hpp"
-
-#include "slave/containerizer/launcher.hpp"
-#ifdef __linux__
-#include "slave/containerizer/fetcher.hpp"
-#include "slave/containerizer/linux_launcher.hpp"
-
-#include "slave/containerizer/mesos/containerizer.hpp"
-#include "slave/containerizer/mesos/launch.hpp"
-#endif // __linux__
-
-#include "tests/flags.hpp"
-#include "tests/memory_test_helper.hpp"
-#include "tests/mesos.hpp"
-#include "tests/module.hpp"
-#include "tests/utils.hpp"
-
-using namespace process;
-
-using mesos::internal::master::Master;
-#ifdef __linux__
-using mesos::internal::slave::CgroupsCpushareIsolatorProcess;
-using mesos::internal::slave::CgroupsMemIsolatorProcess;
-using mesos::internal::slave::CgroupsPerfEventIsolatorProcess;
-using mesos::internal::slave::CPU_SHARES_PER_CPU_REVOCABLE;
-using mesos::internal::slave::Fetcher;
-using mesos::internal::slave::LinuxLauncher;
-using mesos::internal::slave::SharedFilesystemIsolatorProcess;
-#endif // __linux__
-using mesos::internal::slave::Launcher;
-using mesos::internal::slave::MesosContainerizer;
-using mesos::internal::slave::PosixLauncher;
-using mesos::internal::slave::PosixCpuIsolatorProcess;
-using mesos::internal::slave::PosixMemIsolatorProcess;
-using mesos::internal::slave::Slave;
-
-using mesos::slave::Isolator;
-using mesos::slave::IsolatorProcess;
-
-using std::ostringstream;
-using std::set;
-using std::string;
-using std::vector;
-
-using testing::_;
-using testing::DoAll;
-using testing::Return;
-using testing::SaveArg;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-static int childSetup(int pipes[2])
-{
- // In child process.
- while (::close(pipes[1]) == -1 && errno == EINTR);
-
- // Wait until the parent signals us to continue.
- char dummy;
- ssize_t length;
- while ((length = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
- errno == EINTR);
-
- if (length != sizeof(dummy)) {
- ABORT("Failed to synchronize with parent");
- }
-
- while (::close(pipes[0]) == -1 && errno == EINTR);
-
- return 0;
-}
-
-
-template <typename T>
-class CpuIsolatorTest : public MesosTest {};
-
-
-typedef ::testing::Types<
- PosixCpuIsolatorProcess,
-#ifdef __linux__
- CgroupsCpushareIsolatorProcess,
-#endif // __linux__
- tests::Module<Isolator, TestCpuIsolator>> CpuIsolatorTypes;
-
-
-TYPED_TEST_CASE(CpuIsolatorTest, CpuIsolatorTypes);
-
-
-TYPED_TEST(CpuIsolatorTest, UserCpuUsage)
-{
- 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("cpus:1.0").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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- const string& file = path::join(dir.get(), "mesos_isolator_test_ready");
-
- // Max out a single core in userspace. This will run for at most one second.
- string command = "while true ; do true ; done &"
- "touch " + file + "; " // Signals the command is running.
- "sleep 60";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- vector<string> argv(3);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = command;
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- lambda::bind(&childSetup, pipes));
-
- ASSERT_SOME(pid);
-
- // Reap 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 for the command to start.
- while (!os::exists(file));
-
- // Wait up to 1 second for the child process to induce 1/8 of a second of
- // user cpu time.
- ResourceStatistics statistics;
- 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.cpus_user_time_secs() >= 0.125) {
- break;
- }
-
- os::sleep(Milliseconds(200));
- waited += Milliseconds(200);
- } while (waited < Seconds(1));
-
- EXPECT_LE(0.125, statistics.cpus_user_time_secs());
-
- // 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();
-}
-
-
-TYPED_TEST(CpuIsolatorTest, SystemCpuUsage)
-{
- 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("cpus:1.0").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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- const string& file = path::join(dir.get(), "mesos_isolator_test_ready");
-
- // Generating random numbers is done by the kernel and will max out a single
- // core and run almost exclusively in the kernel, i.e., system time.
- string command = "cat /dev/urandom > /dev/null & "
- "touch " + file + "; " // Signals the command is running.
- "sleep 60";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- vector<string> argv(3);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = command;
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- lambda::bind(&childSetup, pipes));
-
- ASSERT_SOME(pid);
-
- // Reap 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 for the command to start.
- while (!os::exists(file));
-
- // Wait up to 1 second for the child process to induce 1/8 of a second of
- // system cpu time.
- ResourceStatistics statistics;
- 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.cpus_system_time_secs() >= 0.125) {
- break;
- }
-
- os::sleep(Milliseconds(200));
- waited += Milliseconds(200);
- } while (waited < Seconds(1));
-
- EXPECT_LE(0.125, statistics.cpus_system_time_secs());
-
- // 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();
-}
-
-
-#ifdef __linux__
-class RevocableCpuIsolatorTest : public MesosTest {};
-
-
-TEST_F(RevocableCpuIsolatorTest, ROOT_CGROUPS_RevocableCpu)
-{
- slave::Flags flags;
-
- Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher = PosixLauncher::create(flags);
-
- // Include revocable CPU in the executor's resources.
- Resource cpu = Resources::parse("cpus", "1", "*").get();
- cpu.mutable_revocable();
-
- ExecutorInfo executorInfo;
- executorInfo.add_resources()->CopyFrom(cpu);
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- os::getcwd(),
- None(),
- None()));
-
- vector<string> argv{"sleep", "100"};
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sleep",
- argv,
- Subprocess::PATH("/dev/null"),
- Subprocess::PATH("/dev/null"),
- Subprocess::PATH("/dev/null"),
- None(),
- None(),
- None());
-
- ASSERT_SOME(pid);
-
- AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));
-
- // Executor should have proper cpu.shares for revocable containers.
- Result<string> cpuHierarchy = cgroups::hierarchy("cpu");
- ASSERT_SOME(cpuHierarchy);
-
- Result<string> cpuCgroup = cgroups::cpu::cgroup(pid.get());
- ASSERT_SOME(cpuCgroup);
-
- EXPECT_SOME_EQ(
- CPU_SHARES_PER_CPU_REVOCABLE,
- cgroups::cpu::shares(cpuHierarchy.get(), cpuCgroup.get()));
-
- // Kill the container and clean up.
- Future<Option<int>> status = process::reap(pid.get());
-
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- AWAIT_READY(status);
-
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-#endif // __linux__
-
-
-#ifdef __linux__
-class LimitedCpuIsolatorTest : public MesosTest {};
-
-
-TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs)
-{
- slave::Flags flags;
-
- // Enable CFS to cap CPU utilization.
- flags.cgroups_enable_cfs = true;
-
- Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources to 0.5 cpu.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse("cpus:0.5").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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- // Generate random numbers to max out a single core. We'll run this for 0.5
- // seconds of wall time so it should consume approximately 250 ms of total
- // cpu time when limited to 0.5 cpu. We use /dev/urandom to prevent blocking
- // on Linux when there's insufficient entropy.
- string command = "cat /dev/urandom > /dev/null & "
- "export MESOS_TEST_PID=$! && "
- "sleep 0.5 && "
- "kill $MESOS_TEST_PID";
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- vector<string> argv(3);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = command;
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- lambda::bind(&childSetup, pipes));
-
- ASSERT_SOME(pid);
-
- // Reap 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 for the command to complete.
- AWAIT_READY(status);
-
- Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
-
- // Expect that no more than 300 ms of cpu time has been consumed. We also
- // check that at least 50 ms of cpu time has been consumed so this test will
- // fail if the host system is very heavily loaded. This behavior is correct
- // because under such conditions we aren't actually testing the CFS cpu
- // limiter.
- double cpuTime = usage.get().cpus_system_time_secs() +
- usage.get().cpus_user_time_secs();
-
- EXPECT_GE(0.30, cpuTime);
- EXPECT_LE(0.05, cpuTime);
-
- // 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();
-}
-
-
-// This test verifies that we can successfully launch a container with
-// a big (>= 10 cpus) cpu quota. This is to catch the regression
-// observed in MESOS-1049.
-// TODO(vinod): Revisit this if/when the isolator restricts the number
-// of cpus that an executor can use based on the slave cpus.
-TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs_Big_Quota)
-{
- slave::Flags flags;
-
- // Enable CFS to cap CPU utilization.
- flags.cgroups_enable_cfs = true;
-
- Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Set the executor's resources to 100.5 cpu.
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse("cpus:100.5").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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- vector<string> argv(3);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = "exit 0";
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- lambda::bind(&childSetup, pipes));
-
- ASSERT_SOME(pid);
-
- // Reap 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 for the command to complete successfully.
- AWAIT_READY(status);
- ASSERT_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();
-}
-
-
-// A test to verify the number of processes and threads in a
-// container.
-TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Pids_and_Tids)
-{
- slave::Flags flags;
- flags.cgroups_cpu_enable_pids_and_tids_count = true;
-
- Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse("cpus:0.5;mem:512").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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- // Right after the creation of the cgroup, which happens in
- // 'prepare', we check that it is empty.
- Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
- EXPECT_EQ(0U, usage.get().processes());
- EXPECT_EQ(0U, usage.get().threads());
-
- int pipes[2];
- ASSERT_NE(-1, ::pipe(pipes));
-
- vector<string> argv(3);
- argv[0] = "sh";
- argv[1] = "-c";
- argv[2] = "while true; do sleep 1; done;";
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- lambda::bind(&childSetup, pipes));
-
- ASSERT_SOME(pid);
-
- // Reap the forked child.
- Future<Option<int>> status = process::reap(pid.get());
-
- // Continue in the parent.
- ASSERT_SOME(os::close(pipes[0]));
-
- // Before isolation, the cgroup is empty.
- usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
- EXPECT_EQ(0U, usage.get().processes());
- EXPECT_EQ(0U, usage.get().threads());
-
- // Isolate the forked child.
- AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));
-
- // After the isolation, the cgroup is not empty, even though the
- // process hasn't exec'd yet.
- usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
- EXPECT_EQ(1U, usage.get().processes());
- EXPECT_EQ(1U, usage.get().threads());
-
- // Now signal the child to continue.
- char dummy;
- ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
-
- ASSERT_SOME(os::close(pipes[1]));
-
- // Process count should be 1 since 'sleep' is still sleeping.
- usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
- EXPECT_EQ(1U, usage.get().processes());
- EXPECT_EQ(1U, usage.get().threads());
-
- // Ensure all processes are killed.
- AWAIT_READY(launcher.get()->destroy(containerId));
-
- // Wait for the command to complete.
- AWAIT_READY(status);
-
- // After the process is killed, the cgroup should be empty again.
- usage = isolator.get()->usage(containerId);
- AWAIT_READY(usage);
- EXPECT_EQ(0U, usage.get().processes());
- EXPECT_EQ(0U, usage.get().threads());
-
- // Let the isolator clean up.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
- delete launcher.get();
-}
-#endif // __linux__
-
-
-template <typename T>
-class MemIsolatorTest : public MesosTest {};
-
-
-typedef ::testing::Types<
- PosixMemIsolatorProcess,
-#ifdef __linux__
- CgroupsMemIsolatorProcess,
-#endif // __linux__
- tests::Module<Isolator, TestMemIsolator>> MemIsolatorTypes;
-
-
-TYPED_TEST_CASE(MemIsolatorTest, MemIsolatorTypes);
-
-
-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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- 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();
-}
-
-
-#ifdef __linux__
-class PerfEventIsolatorTest : public MesosTest {};
-
-
-TEST_F(PerfEventIsolatorTest, ROOT_CGROUPS_Sample)
-{
- slave::Flags flags;
-
- flags.perf_events = "cycles,task-clock";
- flags.perf_duration = Milliseconds(250);
- flags.perf_interval = Milliseconds(500);
-
- Try<Isolator*> isolator = CgroupsPerfEventIsolatorProcess::create(flags);
- ASSERT_SOME(isolator);
-
- ExecutorInfo executorInfo;
-
- 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);
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- dir.get(),
- None(),
- None()));
-
- // This first sample is likely to be empty because perf hasn't
- // completed yet but we should still have the required fields.
- Future<ResourceStatistics> statistics1 = isolator.get()->usage(containerId);
- AWAIT_READY(statistics1);
- ASSERT_TRUE(statistics1.get().has_perf());
- EXPECT_TRUE(statistics1.get().perf().has_timestamp());
- EXPECT_TRUE(statistics1.get().perf().has_duration());
-
- // Wait until we get the next sample. We use a generous timeout of
- // two seconds because we currently have a one second reap interval;
- // when running perf with perf_duration of 250ms we won't notice the
- // exit for up to one second.
- ResourceStatistics statistics2;
- Duration waited = Duration::zero();
- do {
- Future<ResourceStatistics> statistics = isolator.get()->usage(containerId);
- AWAIT_READY(statistics);
-
- statistics2 = statistics.get();
-
- ASSERT_TRUE(statistics2.has_perf());
-
- if (statistics1.get().perf().timestamp() !=
- statistics2.perf().timestamp()) {
- break;
- }
-
- os::sleep(Milliseconds(250));
- waited += Milliseconds(250);
- } while (waited < Seconds(2));
-
- sleep(2);
-
- EXPECT_NE(statistics1.get().perf().timestamp(),
- statistics2.perf().timestamp());
-
- EXPECT_TRUE(statistics2.perf().has_cycles());
- EXPECT_LE(0u, statistics2.perf().cycles());
-
- EXPECT_TRUE(statistics2.perf().has_task_clock());
- EXPECT_LE(0.0, statistics2.perf().task_clock());
-
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
-}
-
-
-class SharedFilesystemIsolatorTest : public MesosTest {};
-
-
-// Test that a container can create a private view of a system
-// directory (/var/tmp). Check that a file written by a process inside
-// the container doesn't appear on the host filesystem but does appear
-// under the container's work directory.
-// This test is disabled since we're planning to remove the shared
-// filesystem isolator and this test is not working on other distros
-// such as CentOS 7.1
-// TODO(tnachen): Remove this test when shared filesystem isolator
-// is removed.
-TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_RelativeVolume)
-{
- slave::Flags flags = CreateSlaveFlags();
- flags.isolation = "filesystem/shared";
-
- Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // Use /var/tmp so we don't mask the work directory (under /tmp).
- const string containerPath = "/var/tmp";
- ASSERT_TRUE(os::stat::isdir(containerPath));
-
- // Use a host path relative to the container work directory.
- const string hostPath = strings::remove(containerPath, "/", strings::PREFIX);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::MESOS);
- containerInfo.add_volumes()->CopyFrom(
- CREATE_VOLUME(containerPath, hostPath, Volume::RW));
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- Future<Option<CommandInfo> > prepare =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- flags.work_dir,
- None(),
- None());
-
- AWAIT_READY(prepare);
- ASSERT_SOME(prepare.get());
-
- // The test will touch a file in container path.
- const string file = path::join(containerPath, UUID::random().toString());
- ASSERT_FALSE(os::exists(file));
-
- // Manually run the isolator's preparation command first, then touch
- // the file.
- vector<string> args;
- args.push_back("/bin/sh");
- args.push_back("-x");
- args.push_back("-c");
- args.push_back(prepare.get().get().value() + " && touch " + file);
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- args,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- None());
- ASSERT_SOME(pid);
-
- // Set up the reaper to wait on the forked child.
- Future<Option<int> > status = process::reap(pid.get());
-
- AWAIT_READY(status);
- EXPECT_SOME_EQ(0, status.get());
-
- // Check the correct hierarchy was created under the container work
- // directory.
- string dir = "/";
- foreach (const string& subdir, strings::tokenize(containerPath, "/")) {
- dir = path::join(dir, subdir);
-
- struct stat hostStat;
- EXPECT_EQ(0, ::stat(dir.c_str(), &hostStat));
-
- struct stat containerStat;
- EXPECT_EQ(0,
- ::stat(path::join(flags.work_dir, dir).c_str(), &containerStat));
-
- EXPECT_EQ(hostStat.st_mode, containerStat.st_mode);
- EXPECT_EQ(hostStat.st_uid, containerStat.st_uid);
- EXPECT_EQ(hostStat.st_gid, containerStat.st_gid);
- }
-
- // Check it did *not* create a file in the host namespace.
- EXPECT_FALSE(os::exists(file));
-
- // Check it did create the file under the container's work directory
- // on the host.
- EXPECT_TRUE(os::exists(path::join(flags.work_dir, file)));
-
- delete launcher.get();
- delete isolator.get();
-}
-
-
-// This test is disabled since we're planning to remove the shared
-// filesystem isolator and this test is not working on other distros
-// such as CentOS 7.1
-// TODO(tnachen): Remove this test when shared filesystem isolator
-// is removed.
-TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_AbsoluteVolume)
-{
- slave::Flags flags = CreateSlaveFlags();
- flags.isolation = "filesystem/shared";
-
- Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
- CHECK_SOME(isolator);
-
- Try<Launcher*> launcher =
- LinuxLauncher::create(flags, isolator.get()->namespaces().get());
- CHECK_SOME(launcher);
-
- // We'll mount the absolute test work directory as /var/tmp in the
- // container.
- const string hostPath = flags.work_dir;
- const string containerPath = "/var/tmp";
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::MESOS);
- containerInfo.add_volumes()->CopyFrom(
- CREATE_VOLUME(containerPath, hostPath, Volume::RW));
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- Future<Option<CommandInfo> > prepare =
- isolator.get()->prepare(
- containerId,
- executorInfo,
- flags.work_dir,
- None(),
- None());
-
- AWAIT_READY(prepare);
- ASSERT_SOME(prepare.get());
-
- // Test the volume mounting by touching a file in the container's
- // /tmp, which should then be in flags.work_dir.
- const string filename = UUID::random().toString();
- ASSERT_FALSE(os::exists(path::join(containerPath, filename)));
-
- vector<string> args;
- args.push_back("/bin/sh");
- args.push_back("-x");
- args.push_back("-c");
- args.push_back(prepare.get().get().value() +
- " && touch " +
- path::join(containerPath, filename));
-
- Try<pid_t> pid = launcher.get()->fork(
- containerId,
- "/bin/sh",
- args,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- None());
- ASSERT_SOME(pid);
-
- // Set up the reaper to wait on the forked child.
- Future<Option<int> > status = process::reap(pid.get());
-
- AWAIT_READY(status);
- EXPECT_SOME_EQ(0, status.get());
-
- // Check the file was created in flags.work_dir.
- EXPECT_TRUE(os::exists(path::join(hostPath, filename)));
-
- // Check it didn't get created in the host's view of containerPath.
- EXPECT_FALSE(os::exists(path::join(containerPath, filename)));
-
- delete launcher.get();
- delete isolator.get();
-}
-
-
-class NamespacesPidIsolatorTest : public MesosTest {};
-
-
-TEST_F(NamespacesPidIsolatorTest, ROOT_PidNamespace)
-{
- slave::Flags flags = CreateSlaveFlags();
- flags.isolation = "namespaces/pid";
-
- string directory = os::getcwd(); // We're inside a temporary sandbox.
-
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer =
- MesosContainerizer::create(flags, false, &fetcher);
- ASSERT_SOME(containerizer);
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- // Write the command's pid namespace inode and init name to files.
- const string command =
- "stat -c %i /proc/self/ns/pid > ns && (cat /proc/1/comm > init)";
-
- process::Future<bool> launch = containerizer.get()->launch(
- containerId,
- CREATE_EXECUTOR_INFO("executor", command),
- directory,
- None(),
- SlaveID(),
- process::PID<Slave>(),
- false);
- AWAIT_READY(launch);
- ASSERT_TRUE(launch.get());
-
- // Wait on the container.
- process::Future<containerizer::Termination> wait =
- containerizer.get()->wait(containerId);
- AWAIT_READY(wait);
-
- // Check the executor exited correctly.
- EXPECT_TRUE(wait.get().has_status());
- EXPECT_EQ(0, wait.get().status());
-
- // Check that the command was run in a different pid namespace.
- Try<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
- ASSERT_SOME(testPidNamespace);
-
- Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
- ASSERT_SOME(containerPidNamespace);
-
- EXPECT_NE(stringify(testPidNamespace.get()),
- strings::trim(containerPidNamespace.get()));
-
- // Check that 'sh' is the container's 'init' process.
- // This verifies that /proc has been correctly mounted for the container.
- Try<string> init = os::read(path::join(directory, "init"));
- ASSERT_SOME(init);
-
- EXPECT_EQ("sh", strings::trim(init.get()));
-
- delete containerizer.get();
-}
-
-
-// Username for the unprivileged user that will be created to test
-// unprivileged cgroup creation. It will be removed after the tests.
-// It is presumed this user does not normally exist.
-const string UNPRIVILEGED_USERNAME = "mesos.test.unprivileged.user";
-
-
-template <typename T>
-class UserCgroupIsolatorTest : public MesosTest
-{
-public:
- static void SetUpTestCase()
- {
- // Remove the user in case it wasn't cleaned up from a previous
- // test.
- os::system("userdel -r " + UNPRIVILEGED_USERNAME + " > /dev/null");
-
- ASSERT_EQ(0, os::system("useradd " + UNPRIVILEGED_USERNAME));
- }
-
-
- static void TearDownTestCase()
- {
- ASSERT_EQ(0, os::system("userdel -r " + UNPRIVILEGED_USERNAME));
- }
-};
-
-
-// Test all isolators that use cgroups.
-typedef ::testing::Types<
- CgroupsMemIsolatorProcess,
- CgroupsCpushareIsolatorProcess,
- CgroupsPerfEventIsolatorProcess> CgroupsIsolatorTypes;
-
-
-TYPED_TEST_CASE(UserCgroupIsolatorTest, CgroupsIsolatorTypes);
-
-
-TYPED_TEST(UserCgroupIsolatorTest, ROOT_CGROUPS_UserCgroup)
-{
- slave::Flags flags;
- flags.perf_events = "cpu-cycles"; // Needed for CgroupsPerfEventIsolator.
-
- Try<Isolator*> isolator = TypeParam::create(flags);
- ASSERT_SOME(isolator);
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_resources()->CopyFrom(
- Resources::parse("mem:1024;cpus:1").get()); // For cpu/mem isolators.
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- AWAIT_READY(isolator.get()->prepare(
- containerId,
- executorInfo,
- os::getcwd(),
- None(),
- UNPRIVILEGED_USERNAME));
-
- // Isolators don't provide a way to determine the cgroups they use
- // so we'll inspect the cgroups for an isolated dummy process.
- pid_t pid = fork();
- if (pid == 0) {
- // Child just sleeps.
- ::sleep(100);
-
- ABORT("Child process should not reach here");
- }
- ASSERT_GT(pid, 0);
-
- AWAIT_READY(isolator.get()->isolate(containerId, pid));
-
- // Get the container's cgroups from /proc/$PID/cgroup. We're only
- // interested in the cgroups that this isolator has created which we
- // can do explicitly by selecting those that have the path that
- // corresponds to the 'cgroups_root' slave flag. For example:
- //
- // $ cat /proc/pid/cgroup
- // 6:blkio:/
- // 5:perf_event:/
- // 4:memory:/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c
- // 3:freezer:/
- // 2:cpuacct:/
- // 1:cpu:/
- //
- // Our 'grep' will only select the 'memory' line and then 'awk' will
- // output 'memory/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c'.
- ostringstream output;
- Try<int> status = os::shell(
- &output,
- "grep '" + path::join("/", flags.cgroups_root) + "' /proc/" +
- stringify(pid) + "/cgroup | awk -F ':' '{print $2$3}'");
-
- ASSERT_SOME(status);
-
- // Kill the dummy child process.
- ::kill(pid, SIGKILL);
- int exitStatus;
- EXPECT_NE(-1, ::waitpid(pid, &exitStatus, 0));
-
- vector<string> cgroups = strings::tokenize(output.str(), "\n");
- ASSERT_FALSE(cgroups.empty());
-
- foreach (const string& cgroup, cgroups) {
- // Check the user cannot manipulate the container's cgroup control
- // files.
- EXPECT_NE(0, os::system(
- "su - " + UNPRIVILEGED_USERNAME +
- " -c 'echo $$ >" +
- path::join(flags.cgroups_hierarchy, cgroup, "cgroup.procs") +
- "'"));
-
- // Check the user can create a cgroup under the container's
- // cgroup.
- string userCgroup = path::join(cgroup, "user");
-
- EXPECT_EQ(0, os::system(
- "su - " +
- UNPRIVILEGED_USERNAME +
- " -c 'mkdir " +
- path::join(flags.cgroups_hierarchy, userCgroup) +
- "'"));
-
- // Check the user can manipulate control files in the created
- // cgroup.
- EXPECT_EQ(0, os::system(
- "su - " +
- UNPRIVILEGED_USERNAME +
- " -c 'echo $$ >" +
- path::join(flags.cgroups_hierarchy, userCgroup, "cgroup.procs") +
- "'"));
- }
-
- // Clean up the container. This will also remove the nested cgroups.
- AWAIT_READY(isolator.get()->cleanup(containerId));
-
- delete isolator.get();
-}
-#endif // __linux__
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/launch_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/launch_tests.cpp b/src/tests/launch_tests.cpp
deleted file mode 100644
index 73c8c5f..0000000
--- a/src/tests/launch_tests.cpp
+++ /dev/null
@@ -1,238 +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 <string>
-#include <vector>
-
-#include <gmock/gmock.h>
-
-#include <stout/foreach.hpp>
-#include <stout/gtest.hpp>
-#include <stout/os.hpp>
-#include <stout/try.hpp>
-
-#include <process/gtest.hpp>
-#include <process/io.hpp>
-#include <process/reap.hpp>
-#include <process/subprocess.hpp>
-
-#include "mesos/resources.hpp"
-
-#include "slave/containerizer/mesos/launch.hpp"
-
-#include "linux/fs.hpp"
-
-#include "tests/flags.hpp"
-#include "tests/utils.hpp"
-
-using namespace process;
-
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class Chroot
-{
-public:
- Chroot(const string& _rootfs)
- : rootfs(_rootfs) {}
-
- virtual ~Chroot() {}
-
- virtual Try<Subprocess> run(const string& command) = 0;
-
- const string rootfs;
-};
-
-
-class BasicLinuxChroot : public Chroot
-{
-public:
- static Try<Owned<Chroot>> create(const string& rootfs)
- {
- if (!os::exists(rootfs)) {
- return Error("rootfs does not exist");
- }
-
- if (os::system("cp -r /bin " + rootfs + "/") != 0) {
- return ErrnoError("Failed to copy /bin to chroot");
- }
-
- if (os::system("cp -r /lib " + rootfs + "/") != 0) {
- return ErrnoError("Failed to copy /lib to chroot");
- }
-
- if (os::system("cp -r /lib64 " + rootfs + "/") != 0) {
- return ErrnoError("Failed to copy /lib64 to chroot");
- }
-
- vector<string> directories = {"proc", "sys", "dev", "tmp"};
- foreach (const string& directory, directories) {
- Try<Nothing> mkdir = os::mkdir(path::join(rootfs, directory));
- if (mkdir.isError()) {
- return Error("Failed to create /" + directory + " in chroot: " +
- mkdir.error());
- }
- }
-
- // We need to bind mount the rootfs so we can pivot on it.
- Try<Nothing> mount =
- fs::mount(rootfs, rootfs, None(), MS_BIND | MS_SLAVE, NULL);
-
- if (mount.isError()) {
- return Error("Failed to bind mount chroot rootfs: " + mount.error());
- }
-
- return Owned<Chroot>(new BasicLinuxChroot(rootfs));
- }
-
- virtual Try<Subprocess> run(const string& _command)
- {
- slave::MesosContainerizerLaunch::Flags launchFlags;
-
- CommandInfo command;
- command.set_value(_command);
-
- launchFlags.command = JSON::Protobuf(command);
- launchFlags.directory = "/tmp";
- launchFlags.pipe_read = open("/dev/zero", O_RDONLY);
- launchFlags.pipe_write = open("/dev/null", O_WRONLY);
- launchFlags.rootfs = rootfs;
-
- vector<string> argv(2);
- argv[0] = "mesos-containerizer";
- argv[1] = slave::MesosContainerizerLaunch::NAME;
-
- Try<Subprocess> s = subprocess(
- path::join(tests::flags.build_dir, "src", "mesos-containerizer"),
- argv,
- Subprocess::PATH("/dev/null"),
- Subprocess::PIPE(),
- Subprocess::FD(STDERR_FILENO),
- launchFlags,
- None(),
- None(),
- lambda::bind(&clone, lambda::_1));
-
- if (s.isError()) {
- close(launchFlags.pipe_read.get());
- close(launchFlags.pipe_write.get());
- } else {
- s.get().status().onAny([=]() {
- // Close when the subprocess terminates.
- close(launchFlags.pipe_read.get());
- close(launchFlags.pipe_write.get());
- });
- }
-
- return s;
- }
-
-private:
- static pid_t clone(const lambda::function<int()>& f)
- {
- static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
-
- return ::clone(
- _clone,
- &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
- CLONE_NEWNS | SIGCHLD, // Specify SIGCHLD as child termination signal.
- (void*) &f);
- }
-
- static int _clone(void* f)
- {
- const lambda::function<int()>* _f =
- static_cast<const lambda::function<int()>*> (f);
-
- return (*_f)();
- }
-
- BasicLinuxChroot(const string& rootfs) : Chroot(rootfs) {}
-
- ~BasicLinuxChroot()
- {
- // Because the test process has the rootfs as its cwd the umount
- // won't actually happen until the
- // TemporaryDirectoryTest::TearDown() changes back to the original
- // directory.
- fs::unmount(rootfs, MNT_DETACH);
- }
-};
-
-
-template <typename T>
-class LaunchChrootTest : public TemporaryDirectoryTest {};
-
-
-// TODO(idownes): Add tests for OSX chroots.
-typedef ::testing::Types<BasicLinuxChroot> ChrootTypes;
-
-
-TYPED_TEST_CASE(LaunchChrootTest, ChrootTypes);
-
-
-TYPED_TEST(LaunchChrootTest, ROOT_DifferentRoot)
-{
- Try<Owned<Chroot>> chroot = TypeParam::create(os::getcwd());
- ASSERT_SOME(chroot);
-
- // Add /usr/bin/stat into the chroot.
- const string usrbin = path::join(chroot.get()->rootfs, "usr", "bin");
- ASSERT_SOME(os::mkdir(usrbin));
- ASSERT_EQ(0, os::system("cp /usr/bin/stat " + path::join(usrbin, "stat")));
-
- Clock::pause();
-
- Try<Subprocess> s = chroot.get()->run(
- "/usr/bin/stat -c %i / >" + path::join("/", "stat.output"));
-
- CHECK_SOME(s);
-
- // Advance time until the internal reaper reaps the subprocess.
- while (s.get().status().isPending()) {
- Clock::advance(Seconds(1));
- Clock::settle();
- }
-
- AWAIT_ASSERT_READY(s.get().status());
- ASSERT_SOME(s.get().status().get());
-
- int status = s.get().status().get().get();
- ASSERT_TRUE(WIFEXITED(status));
- ASSERT_EQ(0, WEXITSTATUS(status));
-
- // Check the chroot has a different root by comparing the inodes.
- Try<ino_t> self = os::stat::inode("/");
- ASSERT_SOME(self);
-
- Try<string> read = os::read(path::join(chroot.get()->rootfs, "stat.output"));
- CHECK_SOME(read);
-
- Try<ino_t> other = numify<ino_t>(strings::trim(read.get()));
- ASSERT_SOME(other);
-
- EXPECT_NE(self.get(), other.get());
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/launcher.hpp
----------------------------------------------------------------------
diff --git a/src/tests/launcher.hpp b/src/tests/launcher.hpp
deleted file mode 100644
index 78216e0..0000000
--- a/src/tests/launcher.hpp
+++ /dev/null
@@ -1,119 +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 <list>
-#include <map>
-#include <string>
-#include <vector>
-
-#include <gmock/gmock.h>
-
-#include <mesos/mesos.hpp>
-
-#include <mesos/slave/isolator.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-#include <process/subprocess.hpp>
-
-#include <stout/flags.hpp>
-#include <stout/lambda.hpp>
-#include <stout/nothing.hpp>
-#include <stout/option.hpp>
-
-#include "slave/containerizer/launcher.hpp"
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-ACTION_P(InvokeRecover, launcher)
-{
- return launcher->real->recover(arg0);
-}
-
-
-ACTION_P(InvokeFork, launcher)
-{
- return launcher->real->fork(
- arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
-}
-
-
-ACTION_P(InvokeDestroy, launcher)
-{
- return launcher->real->destroy(arg0);
-}
-
-
-class TestLauncher : public slave::Launcher
-{
-public:
- TestLauncher(const process::Owned<slave::Launcher>& _real)
- : real(_real)
- {
- using testing::_;
- using testing::DoDefault;
-
- ON_CALL(*this, recover(_))
- .WillByDefault(InvokeRecover(this));
- EXPECT_CALL(*this, recover(_))
- .WillRepeatedly(DoDefault());
-
- ON_CALL(*this, fork(_, _, _, _, _, _, _, _, _))
- .WillByDefault(InvokeFork(this));
- EXPECT_CALL(*this, fork(_, _, _, _, _, _, _, _, _))
- .WillRepeatedly(DoDefault());
-
- ON_CALL(*this, destroy(_))
- .WillByDefault(InvokeDestroy(this));
- EXPECT_CALL(*this, destroy(_))
- .WillRepeatedly(DoDefault());
- }
-
- ~TestLauncher() {}
-
- MOCK_METHOD1(
- recover,
- process::Future<hashset<ContainerID>>(
- const std::list<mesos::slave::ExecutorRunState>& states));
-
- MOCK_METHOD9(
- fork,
- Try<pid_t>(
- const ContainerID& containerId,
- const std::string& path,
- const std::vector<std::string>& argv,
- const process::Subprocess::IO& in,
- const process::Subprocess::IO& out,
- const process::Subprocess::IO& err,
- const Option<flags::FlagsBase>& flags,
- const Option<std::map<std::string, std::string> >& env,
- const Option<lambda::function<int()> >& setup));
-
- MOCK_METHOD1(
- destroy,
- process::Future<Nothing>(const ContainerID& containerId));
-
- process::Owned<slave::Launcher> real;
-};
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/memory_pressure_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/memory_pressure_tests.cpp b/src/tests/memory_pressure_tests.cpp
deleted file mode 100644
index 8089879..0000000
--- a/src/tests/memory_pressure_tests.cpp
+++ /dev/null
@@ -1,293 +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 <vector>
-
-#include <mesos/resources.hpp>
-#include <mesos/scheduler.hpp>
-
-#include <process/gtest.hpp>
-
-#include <stout/gtest.hpp>
-#include <stout/os.hpp>
-
-#include "master/master.hpp"
-
-#include "slave/slave.hpp"
-
-#include "slave/containerizer/containerizer.hpp"
-#include "slave/containerizer/fetcher.hpp"
-
-#include "messages/messages.hpp"
-
-#include "tests/mesos.hpp"
-
-using namespace process;
-
-using mesos::internal::master::Master;
-
-using mesos::internal::slave::Fetcher;
-using mesos::internal::slave::MesosContainerizer;
-using mesos::internal::slave::Slave;
-
-using std::vector;
-
-using testing::_;
-using testing::Eq;
-using testing::Return;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class MemoryPressureMesosTest : public ContainerizerTest<MesosContainerizer>
-{
-public:
- static void SetUpTestCase()
- {
- // Verify that the dd command and its flags used in a bit are valid
- // on this system.
- ASSERT_EQ(0, os::system("dd count=1 bs=1M if=/dev/zero of=/dev/null"))
- << "Cannot find a compatible 'dd' command";
- }
-};
-
-
-TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_Statistics)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- // We only care about memory cgroup for this test.
- flags.isolation = "cgroups/mem";
- flags.slave_subsystems = None();
-
- Fetcher fetcher;
-
- 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(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers);
- EXPECT_NE(0u, offers.get().size());
-
- Offer offer = offers.get()[0];
-
- // Run a task that triggers memory pressure event. We request 1G
- // disk because we are going to write a 512 MB file repeatedly.
- TaskInfo task = createTask(
- offer.slave_id(),
- Resources::parse("cpus:1;mem:256;disk:1024").get(),
- "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done");
-
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- driver.launchTasks(offer.id(), {task});
-
- AWAIT_READY(status);
- EXPECT_EQ(task.task_id(), status.get().task_id());
- EXPECT_EQ(TASK_RUNNING, status.get().state());
-
- Future<hashset<ContainerID>> containers = containerizer.get()->containers();
- AWAIT_READY(containers);
- ASSERT_EQ(1u, containers.get().size());
-
- ContainerID containerId = *(containers.get().begin());
-
- Duration waited = Duration::zero();
- do {
- Future<ResourceStatistics> usage = containerizer.get()->usage(containerId);
- AWAIT_READY(usage);
-
- if (usage.get().mem_low_pressure_counter() > 0) {
- EXPECT_GE(usage.get().mem_low_pressure_counter(),
- usage.get().mem_medium_pressure_counter());
- EXPECT_GE(usage.get().mem_medium_pressure_counter(),
- usage.get().mem_critical_pressure_counter());
- break;
- }
-
- os::sleep(Milliseconds(100));
- waited += Milliseconds(100);
- } while (waited < Seconds(5));
-
- EXPECT_LE(waited, Seconds(5));
-
- driver.stop();
- driver.join();
-
- Shutdown();
- delete containerizer.get();
-}
-
-
-// Test that memory pressure listening is restarted after recovery.
-TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_SlaveRecovery)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- // We only care about memory cgroup for this test.
- flags.isolation = "cgroups/mem";
- flags.slave_subsystems = None();
-
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer1 =
- MesosContainerizer::create(flags, true, &fetcher);
-
- ASSERT_SOME(containerizer1);
-
- Try<PID<Slave>> slave = StartSlave(containerizer1.get(), 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);
-
- EXPECT_CALL(sched, registered(_, _, _));
-
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(_, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(offers);
- EXPECT_NE(0u, offers.get().size());
-
- Offer offer = offers.get()[0];
-
- // Run a task that triggers memory pressure event. We request 1G
- // disk because we are going to write a 512 MB file repeatedly.
- TaskInfo task = createTask(
- offer.slave_id(),
- Resources::parse("cpus:1;mem:256;disk:1024").get(),
- "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done");
-
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&status))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(status);
- EXPECT_EQ(task.task_id(), status.get().task_id());
- EXPECT_EQ(TASK_RUNNING, status.get().state());
-
- // We restart the slave to let it recover.
- Stop(slave.get());
- delete containerizer1.get();
-
- Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover);
-
- Future<SlaveReregisteredMessage> slaveReregisteredMessage =
- FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _);
-
- // Use the same flags.
- Try<MesosContainerizer*> containerizer2 =
- MesosContainerizer::create(flags, true, &fetcher);
-
- ASSERT_SOME(containerizer2);
-
- slave = StartSlave(containerizer2.get(), flags);
- ASSERT_SOME(slave);
-
- Clock::pause();
-
- AWAIT_READY(_recover);
-
- // Wait for slave to schedule reregister timeout.
- Clock::settle();
-
- // Ensure the slave considers itself recovered.
- Clock::advance(slave::EXECUTOR_REREGISTER_TIMEOUT);
-
- Clock::resume();
-
- // Wait for the slave to re-register.
- AWAIT_READY(slaveReregisteredMessage);
-
- Future<hashset<ContainerID>> containers = containerizer2.get()->containers();
- AWAIT_READY(containers);
- ASSERT_EQ(1u, containers.get().size());
-
- ContainerID containerId = *(containers.get().begin());
-
- Duration waited = Duration::zero();
- do {
- Future<ResourceStatistics> usage = containerizer2.get()->usage(containerId);
- AWAIT_READY(usage);
-
- if (usage.get().mem_low_pressure_counter() > 0) {
- EXPECT_GE(usage.get().mem_low_pressure_counter(),
- usage.get().mem_medium_pressure_counter());
- EXPECT_GE(usage.get().mem_medium_pressure_counter(),
- usage.get().mem_critical_pressure_counter());
- break;
- }
-
- os::sleep(Milliseconds(100));
- waited += Milliseconds(100);
- } while (waited < Seconds(5));
-
- EXPECT_LE(waited, Seconds(5));
-
- driver.stop();
- driver.join();
-
- Shutdown();
- delete containerizer2.get();
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
[08/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper.cpp b/src/tests/containerizer/memory_test_helper.cpp
new file mode 100644
index 0000000..48a3563
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper.cpp
@@ -0,0 +1,321 @@
+/**
+ * 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.n
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <string>
+#include <vector>
+
+#include <stout/bytes.hpp>
+#include <stout/error.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/lambda.hpp>
+#include <stout/os.hpp>
+#include <stout/stringify.hpp>
+#include <stout/strings.hpp>
+#include <stout/try.hpp>
+
+#include "tests/flags.hpp"
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using process::Subprocess;
+
+using std::cerr;
+using std::cin;
+using std::cout;
+using std::endl;
+using std::flush;
+using std::getline;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// Constants used to sync MemoryTestHelper and its subprocess.
+
+// Used by the subprocess to inform that it has started.
+const char STARTED = 'S';
+
+// Used by the subprocess to inform that the work requested is done.
+const char DONE = 'D';
+
+// Used to signal an increaseRSS request.
+const char INCREASE_RSS[] = "INCREASE_RSS";
+
+// Used to signal an increasePageCache request.
+const char INCREASE_PAGE_CACHE[] = "INCREASE_PAGE_CACHE";
+
+
+// This helper allocates and locks specified anonymous memory (RSS).
+// It uses mlock and memset to make sure allocated memory is mapped.
+static Try<void*> allocateRSS(const Bytes& size, bool lock = true)
+{
+ void* rss = NULL;
+
+ if (posix_memalign(&rss, getpagesize(), size.bytes()) != 0) {
+ return ErrnoError("Failed to increase RSS memory, posix_memalign");
+ }
+
+ // Use memset to actually page in the memory in the kernel.
+ memset(rss, 1, size.bytes());
+
+ // Locking a page makes it unevictable in the kernel.
+ if (lock && mlock(rss, size.bytes()) != 0) {
+ return ErrnoError("Failed to lock memory, mlock");
+ }
+
+ return rss;
+}
+
+
+static Try<Nothing> increaseRSS(const vector<string>& tokens)
+{
+ 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");
+ }
+
+ Try<void*> memory = allocateRSS(size.get());
+ if (memory.isError()) {
+ return Error("Failed to allocate RSS memory: " + memory.error());
+ }
+
+ return Nothing();
+}
+
+
+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 = strerror(errno);
+
+ os::close(fd.get());
+ return Error("Failed to fsync: " + message);
+ }
+ }
+
+ os::close(fd.get());
+ return Nothing();
+}
+
+
+MemoryTestHelper::~MemoryTestHelper()
+{
+ cleanup();
+}
+
+
+Try<Nothing> MemoryTestHelper::spawn()
+{
+ if (s.isSome()) {
+ return Error("A subprocess has been spawned already");
+ }
+
+ vector<string> argv;
+ argv.push_back("memory-test-helper");
+ argv.push_back(MemoryTestHelperMain::NAME);
+
+ Try<Subprocess> process = subprocess(
+ path::join(flags.build_dir,
+ "src",
+ "memory-test-helper"),
+ argv,
+ Subprocess::PIPE(),
+ Subprocess::PIPE(),
+ Subprocess::FD(STDERR_FILENO));
+
+ if (process.isError()) {
+ return Error("Failed to spawn a subprocess: " + process.error());
+ }
+
+ s = process.get();
+
+ // Wait for the child to inform it has started before returning.
+ // Otherwise, the user might set the memory limit too earlier, and
+ // cause the child oom-killed because 'ld' could use a lot of
+ // memory.
+ Result<string> read = os::read(s.get().out().get(), sizeof(STARTED));
+ if (!read.isSome() || read.get() != string(sizeof(STARTED), STARTED)) {
+ cleanup();
+ return Error("Failed to sync with the subprocess");
+ }
+
+ return Nothing();
+}
+
+
+void MemoryTestHelper::cleanup()
+{
+ if (s.isSome()) {
+ // We just want to make sure the subprocess is terminated in case
+ // it's stuck, but we don't care about its status. Any error
+ // should have been logged in the subprocess directly.
+ ::kill(s.get().pid(), SIGKILL);
+ ::waitpid(s.get().pid(), NULL, 0);
+ s = None();
+ }
+}
+
+
+Try<pid_t> MemoryTestHelper::pid()
+{
+ if (s.isNone()) {
+ return Error("The subprocess has not been spawned yet");
+ }
+
+ return s.get().pid();
+}
+
+
+// Send a request to the subprocess and wait for its signal that the
+// work has been done.
+Try<Nothing> MemoryTestHelper::requestAndWait(const string& request)
+{
+ if (s.isNone()) {
+ return Error("The subprocess has not been spawned yet");
+ }
+
+ Try<Nothing> write = os::write(s.get().in().get(), request + "\n");
+ if (write.isError()) {
+ cleanup();
+ return Error("Fail to sync with the subprocess: " + write.error());
+ }
+
+ Result<string> read = os::read(s.get().out().get(), sizeof(DONE));
+ if (!read.isSome() || read.get() != string(sizeof(DONE), DONE)) {
+ cleanup();
+ return Error("Failed to sync with the subprocess");
+ }
+
+ return Nothing();
+}
+
+
+Try<Nothing> MemoryTestHelper::increaseRSS(const Bytes& size)
+{
+ return requestAndWait(string(INCREASE_RSS) + " " + stringify(size));
+}
+
+
+Try<Nothing> MemoryTestHelper::increasePageCache(const Bytes& size)
+{
+ return requestAndWait(string(INCREASE_PAGE_CACHE) + " " + stringify(size));
+}
+
+
+const char MemoryTestHelperMain::NAME[] = "MemoryTestHelperMain";
+
+
+int MemoryTestHelperMain::execute()
+{
+ hashmap<string, Try<Nothing>(*)(const vector<string>&)> commands;
+ commands[INCREASE_RSS] = &increaseRSS;
+ commands[INCREASE_PAGE_CACHE] = &increasePageCache;
+
+ // Tell the parent that child has started.
+ cout << STARTED << flush;
+
+ string line;
+ while(cin.good()) {
+ getline(cin, line);
+ vector<string> tokens = strings::tokenize(line, " ");
+
+ if (tokens.empty()) {
+ cerr << "No command from the parent" << endl;
+ return 1;
+ }
+
+ if (!commands.contains(tokens[0])) {
+ cerr << "Unknown command from the parent '" << tokens[0] << "'" << endl;
+ return 1;
+ }
+
+ Try<Nothing> result = commands[tokens[0]](tokens);
+ if (result.isError()) {
+ cerr << result.error();
+ return 1;
+ }
+
+ cout << DONE << flush;
+ }
+
+ if (!cin) {
+ cerr << "Failed to sync with the parent" << endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper.hpp b/src/tests/containerizer/memory_test_helper.hpp
new file mode 100644
index 0000000..11712d7
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper.hpp
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MEMORY_TEST_HELPER_HPP__
+#define __MEMORY_TEST_HELPER_HPP__
+
+#include <process/subprocess.hpp>
+
+#include <stout/bytes.hpp>
+#include <stout/option.hpp>
+#include <stout/subcommand.hpp>
+#include <stout/try.hpp>
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// The abstraction for controlling the memory usage of a subprocess.
+// TODO(chzhcn): Currently, this helper is only supposed to be used by
+// one thread. Consider making it thread safe.
+class MemoryTestHelper
+{
+public:
+ MemoryTestHelper() {};
+ ~MemoryTestHelper();
+
+ // Spawns a subprocess.
+ // TODO(chzhcn): Consider returning a future instead of blocking.
+ Try<Nothing> spawn();
+
+ // Kill and reap the subprocess if exists.
+ // TODO(chzhcn): Consider returning a future instead of blocking.
+ void cleanup();
+
+ // Returns the pid of the subprocess.
+ Try<pid_t> pid();
+
+ // Allocate and lock specified page-aligned anonymous memory (RSS)
+ // in the subprocess. It uses mlock and memset to make sure
+ // allocated memory is mapped.
+ // TODO(chzhcn): Consider returning a future instead of blocking.
+ Try<Nothing> increaseRSS(const Bytes& size);
+
+ // This function attempts to generate requested size of page cache
+ // in the subprocess by using a small buffer and writing it to disk
+ // multiple times.
+ // TODO(chzhcn): Consider returning a future instead of blocking.
+ Try<Nothing> increasePageCache(const Bytes& size = Megabytes(1));
+
+private:
+ Try<Nothing> requestAndWait(const std::string& request);
+
+ Option<process::Subprocess> s;
+};
+
+
+// The actual subprocess behind MemoryTestHelper. It runs in a loop
+// and executes commands passed from stdin.
+class MemoryTestHelperMain : public Subcommand
+{
+public:
+ static const char NAME[];
+
+ MemoryTestHelperMain() : Subcommand(NAME) {};
+
+protected:
+ virtual int execute();
+};
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __MEMORY_TEST_HELPER_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_test_helper_main.cpp b/src/tests/containerizer/memory_test_helper_main.cpp
new file mode 100644
index 0000000..df98cbb
--- /dev/null
+++ b/src/tests/containerizer/memory_test_helper_main.cpp
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stout/subcommand.hpp>
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using mesos::internal::tests::MemoryTestHelperMain;
+
+int main(int argc, char** argv)
+{
+ return Subcommand::dispatch(
+ None(),
+ argc,
+ argv,
+ new MemoryTestHelperMain());
+}
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/ns_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/ns_tests.cpp b/src/tests/containerizer/ns_tests.cpp
new file mode 100644
index 0000000..c71c33f
--- /dev/null
+++ b/src/tests/containerizer/ns_tests.cpp
@@ -0,0 +1,302 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/wait.h>
+
+#include <iostream>
+
+#include <pthread.h>
+#include <unistd.h>
+
+#include <list>
+#include <set>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <stout/gtest.hpp>
+#include <stout/lambda.hpp>
+#include <stout/os.hpp>
+
+#include <process/gtest.hpp>
+#include <process/subprocess.hpp>
+
+#include "linux/ns.hpp"
+
+#include "tests/flags.hpp"
+
+#include "tests/containerizer/setns_test_helper.hpp"
+
+using namespace process;
+
+using std::list;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+// Helper for cloneChild() which expects an int(void*).
+static int cloneChildHelper(void* _func)
+{
+ const lambda::function<int()>* func =
+ static_cast<const lambda::function<int()>*> (_func);
+
+ return (*func)();
+}
+
+
+static pid_t cloneChild(
+ int flags,
+ const lambda::function<int()>& func)
+
+{
+ // 8 MiB stack for child.
+ static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+ return ::clone(
+ cloneChildHelper,
+ &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+ flags | SIGCHLD,
+ (void*) &func);
+}
+
+
+// Test that a child in different namespace(s) can setns back to the
+// root namespace. We must fork a child to test this because setns
+// doesn't support multi-threaded processes (which gtest is).
+TEST(NsTest, ROOT_setns)
+{
+ // Clone then exec the setns-test-helper into a new namespace for
+ // each available namespace.
+ set<string> namespaces = ns::namespaces();
+ ASSERT_FALSE(namespaces.empty());
+
+ int flags = 0;
+
+ foreach (const string& ns, namespaces) {
+ // Skip 'user' namespace because it causes 'clone' to change us
+ // from being user 'root' to user 'nobody', but these tests
+ // require root. See MESOS-3083.
+ if (ns == "user") {
+ continue;
+ }
+
+ Try<int> nstype = ns::nstype(ns);
+ ASSERT_SOME(nstype);
+
+ flags |= nstype.get();
+ }
+
+ vector<string> argv;
+ argv.push_back("setns-test-helper");
+ argv.push_back(SetnsTestHelper::NAME);
+
+ Try<Subprocess> s = subprocess(
+ path::join(tests::flags.build_dir, "src", "setns-test-helper"),
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ None(),
+ lambda::bind(&cloneChild, flags, lambda::_1));
+
+ // Continue in parent.
+ ASSERT_SOME(s);
+
+ // The child should exit 0.
+ Future<Option<int>> status = s.get().status();
+ AWAIT_READY(status);
+
+ ASSERT_SOME(status.get());
+ EXPECT_TRUE(WIFEXITED(status.get().get()));
+ EXPECT_EQ(0, status.get().get());
+}
+
+
+static void* childThread(void* arg)
+{
+ // Newly created threads have PTHREAD_CANCEL_ENABLE and
+ // PTHREAD_CANCEL_DEFERRED so they can be cancelled.
+ while (true) { os::sleep(Seconds(1)); }
+
+ return NULL;
+}
+
+
+// Test that setns correctly refuses to re-associate to a namespace if
+// the caller is multi-threaded.
+TEST(NsTest, ROOT_setnsMultipleThreads)
+{
+ set<string> namespaces = ns::namespaces();
+ EXPECT_LT(0u, namespaces.size());
+
+ // Do not allow multi-threaded environment.
+ pthread_t pthread;
+ ASSERT_EQ(0, pthread_create(&pthread, NULL, childThread, NULL));
+
+ foreach (const string& ns, namespaces) {
+ EXPECT_ERROR(ns::setns(::getpid(), ns));
+ }
+
+ // Terminate the threads.
+ EXPECT_EQ(0, pthread_cancel(pthread));
+ EXPECT_EQ(0, pthread_join(pthread, NULL));
+}
+
+
+// Use a different child function for clone because it requires
+// int(*)(void*).
+static int childGetns(void* arg)
+{
+ // Sleep until killed.
+ while (true) { sleep(1); }
+
+ ABORT("Error, child should be killed before reaching here");
+}
+
+
+// Test that we can get the namespace inodes for a forked child.
+TEST(NsTest, ROOT_getns)
+{
+ set<string> namespaces = ns::namespaces();
+
+ // ns::setns() does not support "pid".
+ namespaces.erase("pid");
+
+ // Use the first other namespace available.
+ ASSERT_FALSE(namespaces.empty());
+ string ns = *(namespaces.begin());
+
+ ASSERT_SOME(ns::getns(::getpid(), ns));
+
+ Try<int> nstype = ns::nstype(ns);
+ ASSERT_SOME(nstype);
+
+ // 8 MiB stack for child.
+ static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+ pid_t pid = clone(
+ childGetns,
+ &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+ SIGCHLD | nstype.get(),
+ NULL);
+
+ ASSERT_NE(-1, pid);
+
+ // Continue in parent.
+ Try<ino_t> nsParent = ns::getns(::getpid(), ns);
+ ASSERT_SOME(nsParent);
+
+ Try<ino_t> nsChild = ns::getns(pid, ns);
+ ASSERT_SOME(nsChild);
+
+ // Child should be in a different namespace.
+ EXPECT_NE(nsParent.get(), nsChild.get());
+
+ // Kill the child process.
+ ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ int status;
+ EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+
+static int childDestroy(void* arg)
+{
+ // Fork a bunch of children.
+ ::fork();
+ ::fork();
+ ::fork();
+
+ // Parent and all children sleep.
+ while (true) { sleep(1); }
+
+ ABORT("Error, child should be killed before reaching here");
+}
+
+
+// Test we can destroy a pid namespace, i.e., kill all processes.
+TEST(NsTest, ROOT_destroy)
+{
+ set<string> namespaces = ns::namespaces();
+
+ if (namespaces.count("pid") == 0) {
+ // Pid namespace is not available.
+ return;
+ }
+
+ Try<int> nstype = ns::nstype("pid");
+ ASSERT_SOME(nstype);
+
+ // 8 MiB stack for child.
+ static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+ pid_t pid = clone(
+ childDestroy,
+ &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+ SIGCHLD | nstype.get(),
+ NULL);
+
+ ASSERT_NE(-1, pid);
+
+ Future<Option<int>> status = process::reap(pid);
+
+ // Ensure the child is in a different pid namespace.
+ Try<ino_t> childNs = ns::getns(pid, "pid");
+ ASSERT_SOME(childNs);
+
+ Try<ino_t> ourNs = ns::getns(::getpid(), "pid");
+ ASSERT_SOME(ourNs);
+
+ ASSERT_NE(ourNs.get(), childNs.get());
+
+ // Kill the child.
+ AWAIT_READY(ns::pid::destroy(childNs.get()));
+
+ AWAIT_READY(status);
+ ASSERT_SOME(status.get());
+ ASSERT_TRUE(WIFSIGNALED(status.get().get()));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
+
+ // Finally, verify that no processes are in the child's pid
+ // namespace, i.e., destroy() also killed all descendants.
+ Try<set<pid_t>> pids = os::pids();
+ ASSERT_SOME(pids);
+
+ foreach (pid_t pid, pids.get()) {
+ Try<ino_t> otherNs = ns::getns(pid, "pid");
+ // pid may have exited since getting the snapshot of pids so
+ // ignore any error.
+ if (otherNs.isSome()) {
+ ASSERT_SOME_NE(childNs.get(), otherNs);
+ }
+ }
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/perf_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/perf_tests.cpp b/src/tests/containerizer/perf_tests.cpp
new file mode 100644
index 0000000..6b3d70f
--- /dev/null
+++ b/src/tests/containerizer/perf_tests.cpp
@@ -0,0 +1,183 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/prctl.h>
+
+#include <set>
+
+#include <gmock/gmock.h>
+
+#include <process/clock.hpp>
+#include <process/gtest.hpp>
+#include <process/reap.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/stringify.hpp>
+
+#include "linux/perf.hpp"
+
+using std::set;
+using std::string;
+
+using namespace process;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class PerfTest : public ::testing::Test {};
+
+
+TEST_F(PerfTest, ROOT_Events)
+{
+ set<string> events;
+ // Valid events.
+ events.insert("cycles");
+ events.insert("task-clock");
+ EXPECT_TRUE(perf::valid(events));
+
+ // Add an invalid event.
+ events.insert("this-is-an-invalid-event");
+ EXPECT_FALSE(perf::valid(events));
+}
+
+
+TEST_F(PerfTest, Parse)
+{
+ // uint64 and floats should be parsed.
+ Try<hashmap<string, mesos::PerfStatistics> > parse =
+ perf::parse("123,cycles\n0.123,task-clock");
+ CHECK_SOME(parse);
+
+ ASSERT_TRUE(parse.get().contains(""));
+ mesos::PerfStatistics statistics = parse.get().get("").get();
+
+ ASSERT_TRUE(statistics.has_cycles());
+ EXPECT_EQ(123u, statistics.cycles());
+ ASSERT_TRUE(statistics.has_task_clock());
+ EXPECT_EQ(0.123, statistics.task_clock());
+
+ // Parse multiple cgroups.
+ parse = perf::parse("123,cycles,cgroup1\n"
+ "456,cycles,cgroup2\n"
+ "0.456,task-clock,cgroup2\n"
+ "0.123,task-clock,cgroup1");
+ CHECK_SOME(parse);
+ EXPECT_FALSE(parse.get().contains(""));
+
+ ASSERT_TRUE(parse.get().contains("cgroup1"));
+ statistics = parse.get().get("cgroup1").get();
+
+ ASSERT_TRUE(statistics.has_cycles());
+ EXPECT_EQ(123u, statistics.cycles());
+ ASSERT_TRUE(statistics.has_task_clock());
+ EXPECT_EQ(0.123, statistics.task_clock());
+
+ ASSERT_TRUE(parse.get().contains("cgroup2"));
+ statistics = parse.get().get("cgroup2").get();
+
+ ASSERT_TRUE(statistics.has_cycles());
+ EXPECT_EQ(456u, statistics.cycles());
+ EXPECT_TRUE(statistics.has_task_clock());
+ EXPECT_EQ(0.456, statistics.task_clock());
+
+ // Statistics reporting <not supported> should not appear.
+ parse = perf::parse("<not supported>,cycles");
+ CHECK_SOME(parse);
+
+ ASSERT_TRUE(parse.get().contains(""));
+ statistics = parse.get().get("").get();
+ EXPECT_FALSE(statistics.has_cycles());
+
+ // Statistics reporting <not counted> should be zero.
+ parse = perf::parse("<not counted>,cycles\n<not counted>,task-clock");
+ CHECK_SOME(parse);
+
+ ASSERT_TRUE(parse.get().contains(""));
+ statistics = parse.get().get("").get();
+
+ EXPECT_TRUE(statistics.has_cycles());
+ EXPECT_EQ(0u, statistics.cycles());
+ EXPECT_TRUE(statistics.has_task_clock());
+ EXPECT_EQ(0.0, statistics.task_clock());
+
+ // Check parsing fails.
+ parse = perf::parse("1,cycles\ngarbage");
+ EXPECT_ERROR(parse);
+
+ parse = perf::parse("1,unknown-field");
+ EXPECT_ERROR(parse);
+}
+
+
+TEST_F(PerfTest, ROOT_SamplePid)
+{
+ // TODO(idownes): Replace this with a Subprocess when it supports
+ // DEATHSIG.
+ // Fork a child which we'll run perf against.
+ pid_t pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ // Kill ourself if the parent dies to prevent leaking the child.
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+ // Spin child to consume cpu cycles.
+ while (true);
+ }
+
+ // Continue in parent.
+ set<string> events;
+ // Hardware event.
+ events.insert("cycles");
+ // Software event.
+ events.insert("task-clock");
+
+ // Sample the child.
+ Duration duration = Milliseconds(100);
+ Future<mesos::PerfStatistics> statistics =
+ perf::sample(events, pid, duration);
+ AWAIT_READY(statistics);
+
+ // Kill the child and reap it.
+ Future<Option<int>> status = reap(pid);
+ kill(pid, SIGKILL);
+ AWAIT_READY(status);
+
+ // Check the sample timestamp is within the last 5 seconds. This is generous
+ // because there's the process reap delay in addition to the sampling
+ // duration.
+ ASSERT_TRUE(statistics.get().has_timestamp());
+ EXPECT_GT(
+ Seconds(5).secs(), Clock::now().secs() - statistics.get().timestamp());
+ EXPECT_EQ(duration.secs(), statistics.get().duration());
+
+ ASSERT_TRUE(statistics.get().has_cycles());
+
+ // TODO(benh): Some Linux distributions (Ubuntu 14.04) fail to
+ // properly sample 'cycles' with 'perf', so we don't explicitly
+ // check the value here. See MESOS-3082.
+ // EXPECT_LT(0u, statistics.get().cycles());
+
+ ASSERT_TRUE(statistics.get().has_task_clock());
+ EXPECT_LT(0.0, statistics.get().task_clock());
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
[07/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/port_mapping_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/port_mapping_tests.cpp b/src/tests/containerizer/port_mapping_tests.cpp
new file mode 100644
index 0000000..45ef97a
--- /dev/null
+++ b/src/tests/containerizer/port_mapping_tests.cpp
@@ -0,0 +1,2296 @@
+/**
+ * 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 {
[05/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/docker_containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/docker_containerizer_tests.cpp b/src/tests/docker_containerizer_tests.cpp
deleted file mode 100644
index 80ed60e..0000000
--- a/src/tests/docker_containerizer_tests.cpp
+++ /dev/null
@@ -1,2955 +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 <gtest/gtest.h>
-
-#include <process/future.hpp>
-#include <process/gmock.hpp>
-#include <process/owned.hpp>
-#include <process/subprocess.hpp>
-
-#include <stout/duration.hpp>
-
-#include "linux/cgroups.hpp"
-
-#include "messages/messages.hpp"
-
-#include "tests/flags.hpp"
-#include "tests/mesos.hpp"
-
-#include "slave/containerizer/docker.hpp"
-#include "slave/containerizer/fetcher.hpp"
-
-#include "slave/paths.hpp"
-#include "slave/slave.hpp"
-#include "slave/state.hpp"
-
-using namespace mesos::internal::slave::paths;
-using namespace mesos::internal::slave::state;
-
-using namespace process;
-
-using mesos::internal::master::Master;
-
-using mesos::internal::slave::DockerContainerizer;
-using mesos::internal::slave::DockerContainerizerProcess;
-using mesos::internal::slave::Fetcher;
-using mesos::internal::slave::Slave;
-
-using process::Future;
-using process::Message;
-using process::PID;
-using process::UPID;
-
-using std::vector;
-using std::list;
-using std::string;
-
-using testing::_;
-using testing::DoAll;
-using testing::DoDefault;
-using testing::Eq;
-using testing::Invoke;
-using testing::Return;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-class MockDocker : public Docker
-{
-public:
- MockDocker(const string& path) : Docker(path)
- {
- EXPECT_CALL(*this, pull(_, _, _))
- .WillRepeatedly(Invoke(this, &MockDocker::_pull));
-
- EXPECT_CALL(*this, stop(_, _, _))
- .WillRepeatedly(Invoke(this, &MockDocker::_stop));
-
- EXPECT_CALL(*this, run(_, _, _, _, _, _, _, _, _))
- .WillRepeatedly(Invoke(this, &MockDocker::_run));
-
- EXPECT_CALL(*this, inspect(_, _))
- .WillRepeatedly(Invoke(this, &MockDocker::_inspect));
- }
-
- MOCK_CONST_METHOD9(
- run,
- process::Future<Nothing>(
- const mesos::ContainerInfo&,
- const mesos::CommandInfo&,
- const std::string&,
- const std::string&,
- const std::string&,
- const Option<mesos::Resources>&,
- const Option<std::map<std::string, std::string>>&,
- const Option<std::string>&,
- const Option<std::string>&));
-
- MOCK_CONST_METHOD3(
- pull,
- process::Future<Docker::Image>(
- const string&,
- const string&,
- bool));
-
- MOCK_CONST_METHOD3(
- stop,
- process::Future<Nothing>(
- const string&,
- const Duration&,
- bool));
-
- MOCK_CONST_METHOD2(
- inspect,
- process::Future<Docker::Container>(
- const string&,
- const Option<Duration>&));
-
- process::Future<Nothing> _run(
- const mesos::ContainerInfo& containerInfo,
- const mesos::CommandInfo& commandInfo,
- const std::string& name,
- const std::string& sandboxDirectory,
- const std::string& mappedDirectory,
- const Option<mesos::Resources>& resources,
- const Option<std::map<std::string, std::string>>& env,
- const Option<std::string>& stdoutPath,
- const Option<std::string>& stderrPath) const
- {
- return Docker::run(
- containerInfo,
- commandInfo,
- name,
- sandboxDirectory,
- mappedDirectory,
- resources,
- env,
- stdoutPath,
- stderrPath);
- }
-
- process::Future<Docker::Image> _pull(
- const string& directory,
- const string& image,
- bool force) const
- {
- return Docker::pull(directory, image, force);
- }
-
- process::Future<Nothing> _stop(
- const string& containerName,
- const Duration& timeout,
- bool remove) const
- {
- return Docker::stop(containerName, timeout, remove);
- }
-
- process::Future<Docker::Container> _inspect(
- const string& containerName,
- const Option<Duration>& retryInterval)
- {
- return Docker::inspect(containerName, retryInterval);
- }
-};
-
-
-class DockerContainerizerTest : public MesosTest
-{
-public:
- static string containerName(
- const SlaveID& slaveId,
- const ContainerID& containerId)
- {
- return slave::DOCKER_NAME_PREFIX + slaveId.value() +
- slave::DOCKER_NAME_SEPERATOR + containerId.value();
- }
-
- enum ContainerState {
- EXISTS,
- RUNNING
- };
-
- static bool exists(
- const process::Shared<Docker>& docker,
- const SlaveID& slaveId,
- const ContainerID& containerId,
- ContainerState state = ContainerState::EXISTS)
- {
- Duration waited = Duration::zero();
- string expectedName = containerName(slaveId, containerId);
-
- do {
- Future<Docker::Container> inspect = docker->inspect(expectedName);
-
- if (!inspect.await(Seconds(3))) {
- return false;
- }
-
- if (inspect.isReady()) {
- switch (state) {
- case ContainerState::RUNNING:
- if (inspect.get().pid.isSome()) {
- return true;
- }
- // Retry looking for running pid until timeout.
- break;
- case ContainerState::EXISTS:
- return true;
- }
- }
-
- os::sleep(Milliseconds(200));
- waited += Milliseconds(200);
- } while (waited < Seconds(5));
-
- return false;
- }
-
- static bool containsLine(
- const vector<string>& lines,
- const string& expectedLine)
- {
- foreach (const string& line, lines) {
- if (line == expectedLine) {
- return true;
- }
- }
-
- return false;
- }
-
- virtual void TearDown()
- {
- Try<Docker*> docker = Docker::create(tests::flags.docker, false);
- ASSERT_SOME(docker);
- Future<list<Docker::Container>> containers =
- docker.get()->ps(true, slave::DOCKER_NAME_PREFIX);
-
- AWAIT_READY(containers);
-
- // Cleanup all mesos launched containers.
- foreach (const Docker::Container& container, containers.get()) {
- AWAIT_READY_FOR(docker.get()->rm(container.id, true), Seconds(30));
- }
-
- delete docker.get();
- }
-};
-
-
-class MockDockerContainerizer : public DockerContainerizer {
-public:
- MockDockerContainerizer(
- const slave::Flags& flags,
- Fetcher* fetcher,
- Shared<Docker> docker)
- : DockerContainerizer(flags, fetcher, docker)
- {
- initialize();
- }
-
- MockDockerContainerizer(const Owned<DockerContainerizerProcess>& process)
- : DockerContainerizer(process)
- {
- initialize();
- }
-
- void initialize()
- {
- // NOTE: See TestContainerizer::setup for why we use
- // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
- // 'ON_CALL' and 'WillByDefault'.
- EXPECT_CALL(*this, launch(_, _, _, _, _, _, _))
- .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_launchExecutor));
-
- EXPECT_CALL(*this, launch(_, _, _, _, _, _, _, _))
- .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_launch));
-
- EXPECT_CALL(*this, update(_, _))
- .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_update));
- }
-
- MOCK_METHOD7(
- launch,
- process::Future<bool>(
- const ContainerID&,
- const ExecutorInfo&,
- const std::string&,
- const Option<std::string>&,
- const SlaveID&,
- const process::PID<slave::Slave>&,
- bool checkpoint));
-
- MOCK_METHOD8(
- launch,
- process::Future<bool>(
- const ContainerID&,
- const TaskInfo&,
- const ExecutorInfo&,
- const std::string&,
- const Option<std::string>&,
- const SlaveID&,
- const process::PID<slave::Slave>&,
- bool checkpoint));
-
- MOCK_METHOD2(
- update,
- process::Future<Nothing>(
- const ContainerID&,
- const Resources&));
-
- // Default 'launch' implementation (necessary because we can't just
- // use &DockerContainerizer::launch with 'Invoke').
- process::Future<bool> _launch(
- const ContainerID& containerId,
- const TaskInfo& taskInfo,
- const ExecutorInfo& executorInfo,
- const string& directory,
- const Option<string>& user,
- const SlaveID& slaveId,
- const PID<Slave>& slavePid,
- bool checkpoint)
- {
- return DockerContainerizer::launch(
- containerId,
- taskInfo,
- executorInfo,
- directory,
- user,
- slaveId,
- slavePid,
- checkpoint);
- }
-
- process::Future<bool> _launchExecutor(
- const ContainerID& containerId,
- const ExecutorInfo& executorInfo,
- const string& directory,
- const Option<string>& user,
- const SlaveID& slaveId,
- const PID<Slave>& slavePid,
- bool checkpoint)
- {
- return DockerContainerizer::launch(
- containerId,
- executorInfo,
- directory,
- user,
- slaveId,
- slavePid,
- checkpoint);
- }
-
- process::Future<Nothing> _update(
- const ContainerID& containerId,
- const Resources& resources)
- {
- return DockerContainerizer::update(
- containerId,
- resources);
- }
-};
-
-
-class MockDockerContainerizerProcess : public DockerContainerizerProcess
-{
-public:
- MockDockerContainerizerProcess(
- const slave::Flags& flags,
- Fetcher* fetcher,
- const Shared<Docker>& docker)
- : DockerContainerizerProcess(flags, fetcher, docker)
- {
- EXPECT_CALL(*this, fetch(_, _))
- .WillRepeatedly(Invoke(this, &MockDockerContainerizerProcess::_fetch));
-
- EXPECT_CALL(*this, pull(_))
- .WillRepeatedly(Invoke(this, &MockDockerContainerizerProcess::_pull));
- }
-
- MOCK_METHOD2(
- fetch,
- process::Future<Nothing>(
- const ContainerID& containerId,
- const SlaveID& slaveId));
-
- MOCK_METHOD1(
- pull,
- process::Future<Nothing>(const ContainerID& containerId));
-
- process::Future<Nothing> _fetch(
- const ContainerID& containerId,
- const SlaveID& slaveId)
- {
- return DockerContainerizerProcess::fetch(containerId, slaveId);
- }
-
- process::Future<Nothing> _pull(const ContainerID& containerId)
- {
- return DockerContainerizerProcess::pull(containerId);
- }
-};
-
-
-// Only enable executor launch on linux as other platforms
-// requires running linux VM and need special port forwarding
-// to get host networking to work.
-#ifdef __linux__
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch_Executor)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- slave::Flags flags = CreateSlaveFlags();
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- ExecutorInfo executorInfo;
- ExecutorID executorId;
- executorId.set_value("e1");
- executorInfo.mutable_executor_id()->CopyFrom(executorId);
-
- CommandInfo command;
- command.set_value("/bin/test-executor");
- executorInfo.mutable_command()->CopyFrom(command);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("tnachen/test-executor");
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- task.mutable_executor()->CopyFrom(executorInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launchExecutor)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- ASSERT_FALSE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- Shutdown();
-}
-
-
-// This test verifies that a custom executor can be launched and
-// registered with the slave with docker bridge network enabled.
-// We're assuming that the custom executor is registering it's public
-// ip instead of 0.0.0.0 or equivelent to the slave as that's the
-// default behavior for libprocess.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch_Executor_Bridged)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- slave::Flags flags = CreateSlaveFlags();
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- ExecutorInfo executorInfo;
- ExecutorID executorId;
- executorId.set_value("e1");
- executorInfo.mutable_executor_id()->CopyFrom(executorId);
-
- CommandInfo command;
- command.set_value("/bin/test-executor");
- executorInfo.mutable_command()->CopyFrom(command);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("tnachen/test-executor");
- dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- task.mutable_executor()->CopyFrom(executorInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launchExecutor)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- ASSERT_FALSE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- Shutdown();
-}
-#endif // __linux__
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- slave::Flags flags = CreateSlaveFlags();
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- ASSERT_TRUE(statusRunning.get().has_data());
-
- Try<JSON::Array> parse = JSON::parse<JSON::Array>(statusRunning.get().data());
- ASSERT_SOME(parse);
-
- // Now verify that the Docker.NetworkSettings.IPAddress label is
- // present.
- ASSERT_TRUE(statusRunning.get().has_labels());
- EXPECT_EQ(1, statusRunning.get().labels().labels().size());
- EXPECT_EQ("Docker.NetworkSettings.IPAddress",
- statusRunning.get().labels().labels(0).key());
-
- ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- ASSERT_FALSE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- Shutdown();
-}
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Kill)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- slave::Flags flags = CreateSlaveFlags();
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
-
- ASSERT_TRUE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- Future<TaskStatus> statusKilled;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusKilled));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.killTask(task.task_id());
-
- AWAIT_READY(statusKilled);
- EXPECT_EQ(TASK_KILLED, statusKilled.get().state());
-
- AWAIT_READY(termination);
-
- ASSERT_FALSE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// This test tests DockerContainerizer::usage().
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Usage)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
- flags.resources = Option<string>("cpus:2;mem:1024");
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- // Run a CPU intensive command, so we can measure utime and stime later.
- command.set_value("dd if=/dev/zero of=/dev/null");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- // We ignore all update calls to prevent resizing cgroup limits.
- EXPECT_CALL(dockerContainerizer, update(_, _))
- .WillRepeatedly(Return(Nothing()));
-
- Future<TaskStatus> statusRunning;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
-
- // Verify the usage.
- ResourceStatistics statistics;
- Duration waited = Duration::zero();
- do {
- Future<ResourceStatistics> usage =
- dockerContainerizer.usage(containerId.get());
- // TODO(tnachen): Replace await with AWAIT_COMPLETED once
- // implemented.
- ASSERT_TRUE(usage.await(Seconds(3)));
-
- if (usage.isReady()) {
- statistics = usage.get();
-
- if (statistics.cpus_user_time_secs() > 0 &&
- statistics.cpus_system_time_secs() > 0) {
- break;
- }
- }
-
- os::sleep(Milliseconds(200));
- waited += Milliseconds(200);
- } while (waited < Seconds(3));
-
- // Usage includes the executor resources.
- EXPECT_EQ(2.0 + slave::DEFAULT_EXECUTOR_CPUS, statistics.cpus_limit());
- EXPECT_EQ((Gigabytes(1) + slave::DEFAULT_EXECUTOR_MEM).bytes(),
- statistics.mem_limit_bytes());
- EXPECT_LT(0, statistics.cpus_user_time_secs());
- EXPECT_LT(0, statistics.cpus_system_time_secs());
- EXPECT_GT(statistics.mem_rss_bytes(), 0u);
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- dockerContainerizer.destroy(containerId.get());
-
- AWAIT_READY(termination);
-
- // Usage() should fail again since the container is destroyed.
- Future<ResourceStatistics> usage =
- dockerContainerizer.usage(containerId.get());
-
- AWAIT_FAILED(usage);
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-#ifdef __linux__
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Update)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(containerId);
-
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
-
- ASSERT_TRUE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- string name = containerName(slaveId, containerId.get());
-
- Future<Docker::Container> inspect = docker->inspect(name);
-
- AWAIT_READY(inspect);
-
- Try<Resources> newResources = Resources::parse("cpus:1;mem:128");
-
- ASSERT_SOME(newResources);
-
- Future<Nothing> update =
- dockerContainerizer.update(containerId.get(), newResources.get());
-
- AWAIT_READY(update);
-
- Result<string> cpuHierarchy = cgroups::hierarchy("cpu");
- Result<string> memoryHierarchy = cgroups::hierarchy("memory");
-
- ASSERT_SOME(cpuHierarchy);
- ASSERT_SOME(memoryHierarchy);
-
- Option<pid_t> pid = inspect.get().pid;
- ASSERT_SOME(pid);
-
- Result<string> cpuCgroup = cgroups::cpu::cgroup(pid.get());
- ASSERT_SOME(cpuCgroup);
-
- Result<string> memoryCgroup = cgroups::memory::cgroup(pid.get());
- ASSERT_SOME(memoryCgroup);
-
- Try<uint64_t> cpu = cgroups::cpu::shares(
- cpuHierarchy.get(),
- cpuCgroup.get());
-
- ASSERT_SOME(cpu);
-
- Try<Bytes> mem = cgroups::memory::soft_limit_in_bytes(
- memoryHierarchy.get(),
- memoryCgroup.get());
-
- ASSERT_SOME(mem);
-
- EXPECT_EQ(1024u, cpu.get());
- EXPECT_EQ(128u, mem.get().megabytes());
-
- newResources = Resources::parse("cpus:1;mem:144");
-
- // Issue second update that uses the cached pid instead of inspect.
- update = dockerContainerizer.update(containerId.get(), newResources.get());
-
- AWAIT_READY(update);
-
- cpu = cgroups::cpu::shares(cpuHierarchy.get(), cpuCgroup.get());
-
- ASSERT_SOME(cpu);
-
- mem = cgroups::memory::soft_limit_in_bytes(
- memoryHierarchy.get(),
- memoryCgroup.get());
-
- ASSERT_SOME(mem);
-
- EXPECT_EQ(1024u, cpu.get());
- EXPECT_EQ(144u, mem.get().megabytes());
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-#endif //__linux__
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Recover)
-{
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Future<string> stoppedContainer;
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillOnce(DoAll(FutureArg<0>(&stoppedContainer),
- Return(Nothing())));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- SlaveID slaveId;
- slaveId.set_value("s1");
- ContainerID containerId;
- containerId.set_value("c1");
- ContainerID reapedContainerId;
- reapedContainerId.set_value("c2");
-
- string container1 = containerName(slaveId, containerId);
- string container2 = containerName(slaveId, reapedContainerId);
-
- // Clean up artifacts if containers still exists.
- ASSERT_TRUE(docker->rm(container1, true).await(Seconds(30)));
- ASSERT_TRUE(docker->rm(container2, true).await(Seconds(30)));
-
- Resources resources = Resources::parse("cpus:1;mem:512").get();
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- CommandInfo commandInfo;
- commandInfo.set_value("sleep 1000");
-
- Future<Nothing> d1 =
- docker->run(
- containerInfo,
- commandInfo,
- container1,
- flags.work_dir,
- flags.sandbox_directory,
- resources);
-
- Future<Nothing> d2 =
- docker->run(
- containerInfo,
- commandInfo,
- container2,
- flags.work_dir,
- flags.sandbox_directory,
- resources);
-
- ASSERT_TRUE(
- exists(docker, slaveId, containerId, ContainerState::RUNNING));
- ASSERT_TRUE(
- exists(docker, slaveId, reapedContainerId, ContainerState::RUNNING));
-
- Future<Docker::Container> inspect = docker->inspect(container2);
- AWAIT_READY(inspect);
-
- SlaveState slaveState;
- slaveState.id = slaveId;
- FrameworkState frameworkState;
-
- ExecutorID execId;
- execId.set_value("e1");
-
- ExecutorState execState;
- ExecutorInfo execInfo;
- execState.info = execInfo;
- execState.latest = containerId;
-
- Try<process::Subprocess> wait =
- process::subprocess(tests::flags.docker + " wait " + container1);
-
- ASSERT_SOME(wait);
-
- FrameworkID frameworkId;
-
- RunState runState;
- runState.id = containerId;
- runState.forkedPid = wait.get().pid();
- execState.runs.put(containerId, runState);
- frameworkState.executors.put(execId, execState);
-
- slaveState.frameworks.put(frameworkId, frameworkState);
-
- Future<Nothing> recover = dockerContainerizer.recover(slaveState);
-
- AWAIT_READY(recover);
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId);
-
- ASSERT_FALSE(termination.isFailed());
-
- AWAIT_FAILED(dockerContainerizer.wait(reapedContainerId));
-
- AWAIT_EQ(inspect.get().id, stoppedContainer);
-
- Shutdown();
-}
-
-
-// This test checks the docker containerizer doesn't recover executors
-// that were started by another containerizer (e.g: mesos).
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_SkipRecoverNonDocker)
-{
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- ContainerID containerId;
- containerId.set_value("c1");
- ContainerID reapedContainerId;
- reapedContainerId.set_value("c2");
-
- ExecutorID executorId;
- executorId.set_value(UUID::random().toString());
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_container()->set_type(ContainerInfo::MESOS);
-
- ExecutorState executorState;
- executorState.info = executorInfo;
- executorState.latest = containerId;
-
- RunState runState;
- runState.id = containerId;
- executorState.runs.put(containerId, runState);
-
- FrameworkState frameworkState;
- frameworkState.executors.put(executorId, executorState);
-
- SlaveState slaveState;
- FrameworkID frameworkId;
- frameworkId.set_value(UUID::random().toString());
- slaveState.frameworks.put(frameworkId, frameworkState);
-
- Future<Nothing> recover = dockerContainerizer.recover(slaveState);
- AWAIT_READY(recover);
-
- Future<hashset<ContainerID>> containers = dockerContainerizer.containers();
- AWAIT_READY(containers);
-
- // A MesosContainerizer task shouldn't be recovered by
- // DockerContainerizer.
- EXPECT_EQ(0u, containers.get().size());
-}
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Logs)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- // We skip stopping the docker container because stopping a container
- // even when it terminated might not flush the logs and we end up
- // not getting stdout/stderr in our tests.
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillRepeatedly(Return(Nothing()));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- string uuid = UUID::random().toString();
-
- CommandInfo command;
- command.set_value("echo out" + uuid + " ; echo err" + uuid + " 1>&2");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- Future<string> directory;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<3>(&directory),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY(directory);
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- // Now check that the proper output is in stderr and stdout (which
- // might also contain other things, hence the use of a UUID).
- Try<string> read = os::read(path::join(directory.get(), "stderr"));
- ASSERT_SOME(read);
-
- vector<string> lines = strings::split(read.get(), "\n");
-
- EXPECT_TRUE(containsLine(lines, "err" + uuid));
- EXPECT_FALSE(containsLine(lines, "out" + uuid));
-
- read = os::read(path::join(directory.get(), "stdout"));
- ASSERT_SOME(read);
-
- lines = strings::split(read.get(), "\n");
-
- EXPECT_TRUE(containsLine(lines, "out" + uuid));
- EXPECT_FALSE(containsLine(lines, "err" + uuid));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// The following test uses a Docker image (mesosphere/inky) that has
-// an entrypoint "echo" and a default command "inky".
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- // We skip stopping the docker container because stopping a container
- // even when it terminated might not flush the logs and we end up
- // not getting stdout/stderr in our tests.
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillRepeatedly(Return(Nothing()));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_shell(false);
-
- // NOTE: By not setting CommandInfo::value we're testing that we
- // will still be able to run the container because it has a default
- // entrypoint!
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("mesosphere/inky");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- Future<string> directory;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<3>(&directory),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY(directory);
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- Try<string> read = os::read(path::join(directory.get(), "stdout"));
- ASSERT_SOME(read);
-
- vector<string> lines = strings::split(read.get(), "\n");
-
- // Since we're not passing any command value, we're expecting the
- // default entry point to be run which is 'echo' with the default
- // command from the image which is 'inky'.
- EXPECT_TRUE(containsLine(lines, "inky"));
-
- read = os::read(path::join(directory.get(), "stderr"));
- ASSERT_SOME(read);
-
- lines = strings::split(read.get(), "\n");
-
- EXPECT_FALSE(containsLine(lines, "inky"));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// The following test uses a Docker image (mesosphere/inky) that has
-// an entrypoint "echo" and a default command "inky".
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD_Override)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- // We skip stopping the docker container because stopping a container
- // even when it terminated might not flush the logs and we end up
- // not getting stdout/stderr in our tests.
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillRepeatedly(Return(Nothing()));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- string uuid = UUID::random().toString();
-
- CommandInfo command;
- command.set_shell(false);
-
- // We can set the value to just the 'uuid' since it should get
- // passed as an argument to the entrypoint, i.e., 'echo uuid'.
- command.set_value(uuid);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("mesosphere/inky");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- Future<string> directory;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<3>(&directory),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY(directory);
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- // Now check that the proper output is in stderr and stdout.
- Try<string> read = os::read(path::join(directory.get(), "stdout"));
- ASSERT_SOME(read);
-
- vector<string> lines = strings::split(read.get(), "\n");
-
- // We expect the passed in command value to override the image's
- // default command, thus we should see the value of 'uuid' in the
- // output instead of the default command which is 'inky'.
- EXPECT_TRUE(containsLine(lines, uuid));
- EXPECT_FALSE(containsLine(lines, "inky"));
-
- read = os::read(path::join(directory.get(), "stderr"));
- ASSERT_SOME(read);
-
- lines = strings::split(read.get(), "\n");
-
- EXPECT_FALSE(containsLine(lines, "inky"));
- EXPECT_FALSE(containsLine(lines, uuid));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// The following test uses a Docker image (mesosphere/inky) that has
-// an entrypoint "echo" and a default command "inky".
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD_Args)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- // We skip stopping the docker container because stopping a container
- // even when it terminated might not flush the logs and we end up
- // not getting stdout/stderr in our tests.
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillRepeatedly(Return(Nothing()));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- string uuid = UUID::random().toString();
-
- CommandInfo command;
- command.set_shell(false);
-
- // We should also be able to skip setting the comamnd value and just
- // set the arguments and those should also get passed through to the
- // entrypoint!
- command.add_arguments(uuid);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("mesosphere/inky");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- Future<string> directory;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<3>(&directory),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY(directory);
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- // Now check that the proper output is in stderr and stdout.
- Try<string> read = os::read(path::join(directory.get(), "stdout"));
- ASSERT_SOME(read);
-
- vector<string> lines = strings::split(read.get(), "\n");
-
- // We expect the passed in command arguments to override the image's
- // default command, thus we should see the value of 'uuid' in the
- // output instead of the default command which is 'inky'.
- EXPECT_TRUE(containsLine(lines, uuid));
- EXPECT_FALSE(containsLine(lines, "inky"));
-
- read = os::read(path::join(directory.get(), "stderr"));
- ASSERT_SOME(read);
-
- lines = strings::split(read.get(), "\n");
-
- EXPECT_FALSE(containsLine(lines, "inky"));
- EXPECT_FALSE(containsLine(lines, uuid));
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// The slave is stopped before the first update for a task is received
-// from the executor. When it comes back up we make sure the executor
-// re-registers and the slave properly sends the update.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_SlaveRecoveryTaskContainer)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // We put the containerizer on the heap so we can more easily
- // control it's lifetime, i.e., when we invoke the destructor.
- MockDockerContainerizer* dockerContainerizer1 =
- new MockDockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave1 = StartSlave(dockerContainerizer1, flags);
- ASSERT_SOME(slave1);
-
- // Enable checkpointing for the framework.
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_checkpoint(true);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(*dockerContainerizer1, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(dockerContainerizer1,
- &MockDockerContainerizer::_launch)));
-
- // Drop the first update from the executor.
- Future<StatusUpdateMessage> statusUpdateMessage =
- DROP_PROTOBUF(StatusUpdateMessage(), _, _);
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(containerId);
-
- // Stop the slave before the status update is received.
- AWAIT_READY(statusUpdateMessage);
-
- Stop(slave1.get());
-
- delete dockerContainerizer1;
-
- Future<Message> reregisterExecutorMessage =
- FUTURE_MESSAGE(Eq(ReregisterExecutorMessage().GetTypeName()), _, _);
-
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(_, _))
- .WillOnce(FutureArg<1>(&status))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- MockDockerContainerizer* dockerContainerizer2 =
- new MockDockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave2 = StartSlave(dockerContainerizer2, flags);
- ASSERT_SOME(slave2);
-
- // Ensure the executor re-registers.
- AWAIT_READY(reregisterExecutorMessage);
- UPID executorPid = reregisterExecutorMessage.get().from;
-
- ReregisterExecutorMessage reregister;
- reregister.ParseFromString(reregisterExecutorMessage.get().body);
-
- // Executor should inform about the unacknowledged update.
- ASSERT_EQ(1, reregister.updates_size());
- const StatusUpdate& update = reregister.updates(0);
- ASSERT_EQ(task.task_id(), update.status().task_id());
- ASSERT_EQ(TASK_RUNNING, update.status().state());
-
- // Scheduler should receive the recovered update.
- AWAIT_READY(status);
- ASSERT_EQ(TASK_RUNNING, status.get().state());
-
- ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer2->wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- Shutdown();
-
- delete dockerContainerizer2;
-}
-
-
-// The slave is stopped before the first update for a task is received
-// from the executor. When it comes back up we make sure the executor
-// re-registers and the slave properly sends the update.
-//
-// TODO(benh): This test is currently disabled because the executor
-// inside the image mesosphere/test-executor does not properly set the
-// executor PID that is uses during registration, so when the new
-// slave recovers it can't reconnect and instead destroys that
-// container. In particular, it uses '0' for it's IP which we properly
-// parse and can even properly use for sending other messages, but the
-// current implementation of 'UPID::operator bool ()' fails if the IP
-// component of a PID is '0'.
-TEST_F(DockerContainerizerTest,
- DISABLED_ROOT_DOCKER_SlaveRecoveryExecutorContainer)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- MockDockerContainerizer* dockerContainerizer1 =
- new MockDockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave1 = StartSlave(dockerContainerizer1, flags);
- ASSERT_SOME(slave1);
-
- // Enable checkpointing for the framework.
- FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
- frameworkInfo.set_checkpoint(true);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- ExecutorInfo executorInfo;
- ExecutorID executorId;
- executorId.set_value("e1");
- executorInfo.mutable_executor_id()->CopyFrom(executorId);
-
- CommandInfo command;
- command.set_value("test-executor");
- executorInfo.mutable_command()->CopyFrom(command);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("mesosphere/test-executor");
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- task.mutable_executor()->CopyFrom(executorInfo);
-
- Future<ContainerID> containerId;
- Future<SlaveID> slaveId;
- EXPECT_CALL(*dockerContainerizer1, launch(_, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<4>(&slaveId),
- Invoke(dockerContainerizer1,
- &MockDockerContainerizer::_launchExecutor)));
-
- // We need to wait until the container's pid has been been
- // checkpointed so that when the next slave recovers it won't treat
- // the executor as having gone lost! We know this has completed
- // after Containerizer::launch returns and the
- // Slave::executorLaunched gets dispatched.
- Future<Nothing> executorLaunched =
- FUTURE_DISPATCH(_, &Slave::executorLaunched);
-
- // The test-executor in the image immediately sends a TASK_RUNNING
- // followed by TASK_FINISHED (no sleep/delay in between) so we need
- // to drop the first TWO updates that come from the executor rather
- // than only the first update like above where we can control how
- // the length of the task.
- Future<StatusUpdateMessage> statusUpdateMessage1 =
- DROP_PROTOBUF(StatusUpdateMessage(), _, _);
-
- // Drop the first update from the executor.
- Future<StatusUpdateMessage> statusUpdateMessage2 =
- DROP_PROTOBUF(StatusUpdateMessage(), _, _);
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY(containerId);
- AWAIT_READY(slaveId);
-
- AWAIT_READY(executorLaunched);
- AWAIT_READY(statusUpdateMessage1);
- AWAIT_READY(statusUpdateMessage2);
-
- Stop(slave1.get());
-
- delete dockerContainerizer1;
-
- Future<Message> reregisterExecutorMessage =
- FUTURE_MESSAGE(Eq(ReregisterExecutorMessage().GetTypeName()), _, _);
-
- Future<TaskStatus> status;
- EXPECT_CALL(sched, statusUpdate(_, _))
- .WillOnce(FutureArg<1>(&status))
- .WillRepeatedly(Return()); // Ignore subsequent updates.
-
- MockDockerContainerizer* dockerContainerizer2 =
- new MockDockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave2 = StartSlave(dockerContainerizer2, flags);
- ASSERT_SOME(slave2);
-
- // Ensure the executor re-registers.
- AWAIT_READY(reregisterExecutorMessage);
- UPID executorPid = reregisterExecutorMessage.get().from;
-
- ReregisterExecutorMessage reregister;
- reregister.ParseFromString(reregisterExecutorMessage.get().body);
-
- // Executor should inform about the unacknowledged update.
- ASSERT_EQ(1, reregister.updates_size());
- const StatusUpdate& update = reregister.updates(0);
- ASSERT_EQ(task.task_id(), update.status().task_id());
- ASSERT_EQ(TASK_RUNNING, update.status().state());
-
- // Scheduler should receive the recovered update.
- AWAIT_READY(status);
- ASSERT_EQ(TASK_RUNNING, status.get().state());
-
- ASSERT_TRUE(exists(docker, slaveId.get(), containerId.get()));
-
- driver.stop();
- driver.join();
-
- delete dockerContainerizer2;
-}
-
-
-// This test verifies that port mapping with bridge network is
-// exposing the host port to the container port, by sending data
-// to the host port and receiving it in the container by listening
-// to the mapped container port.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_NC_PortMapping)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- flags.resources = "cpus:1;mem:1024;ports:[10000-10000]";
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- // We skip stopping the docker container because stopping a container
- // even when it terminated might not flush the logs and we end up
- // not getting stdout/stderr in our tests.
- EXPECT_CALL(*mockDocker, stop(_, _, _))
- .WillRepeatedly(Return(Nothing()));
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_shell(false);
- command.set_value("nc");
- command.add_arguments("-l");
- command.add_arguments("-p");
- command.add_arguments("1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
-
- ContainerInfo::DockerInfo::PortMapping portMapping;
- portMapping.set_host_port(10000);
- portMapping.set_container_port(1000);
-
- dockerInfo.add_port_mappings()->CopyFrom(portMapping);
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- Future<string> directory;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- FutureArg<3>(&directory),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- Future<TaskStatus> statusFinished;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillOnce(FutureArg<1>(&statusFinished))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY(directory);
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
-
- ASSERT_TRUE(
- exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
-
- string uuid = UUID::random().toString();
-
- // Write uuid to docker mapped host port.
- Try<process::Subprocess> s = process::subprocess(
- "echo " + uuid + " | nc localhost 10000");
-
- ASSERT_SOME(s);
- AWAIT_READY_FOR(s.get().status(), Seconds(60));
-
- AWAIT_READY_FOR(statusFinished, Seconds(60));
- EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
-
- // Now check that the proper output is in stdout.
- Try<string> read = os::read(path::join(directory.get(), "stdout"));
- ASSERT_SOME(read);
-
- const vector<string> lines = strings::split(read.get(), "\n");
-
- // We expect the uuid that is sent to host port to be written
- // to stdout by the docker container running nc -l.
- EXPECT_TRUE(containsLine(lines, uuid));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- Shutdown();
-}
-
-
-// This test verifies that sandbox with ':' in the path can still
-// run successfully. This a limitation of the Docker CLI where
-// the volume map parameter treats colons (:) as seperators,
-// and incorrectly seperates the sandbox directory.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_LaunchSandboxWithColon)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- SlaveID slaveId = offer.slave_id();
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("test:colon");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- Future<TaskStatus> statusRunning;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusRunning))
- .WillRepeatedly(DoDefault());
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
- AWAIT_READY_FOR(statusRunning, Seconds(60));
- EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
-
- ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
-
- Future<containerizer::Termination> termination =
- dockerContainerizer.wait(containerId.get());
-
- driver.stop();
- driver.join();
-
- AWAIT_READY(termination);
-
- Shutdown();
-}
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_DestroyWhileFetching)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Promise<Nothing> promise;
- Future<Nothing> fetch;
-
- // We want to pause the fetch call to simulate a long fetch time.
- EXPECT_CALL(*process, fetch(_, _))
- .WillOnce(DoAll(FutureSatisfy(&fetch),
- Return(promise.future())));
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
-
- AWAIT_READY(fetch);
-
- dockerContainerizer.destroy(containerId.get());
-
- // Resume docker launch.
- promise.set(Nothing());
-
- AWAIT_READY(statusFailed);
-
- EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_DestroyWhilePulling)
-{
- Try<PID<Master> > master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Future<Nothing> fetch;
- EXPECT_CALL(*process, fetch(_, _))
- .WillOnce(DoAll(FutureSatisfy(&fetch),
- Return(Nothing())));
-
- Promise<Nothing> promise;
-
- // We want to pause the fetch call to simulate a long fetch time.
- EXPECT_CALL(*process, pull(_))
- .WillOnce(Return(promise.future()));
-
- Try<PID<Slave> > slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer> > offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("sleep 1000");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
-
- // Wait until fetch is finished.
- AWAIT_READY(fetch);
-
- dockerContainerizer.destroy(containerId.get());
-
- // Resume docker launch.
- promise.set(Nothing());
-
- AWAIT_READY(statusFailed);
-
- EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
-
- driver.stop();
- driver.join();
-
- Shutdown();
-}
-
-
-// This test checks that when a docker containerizer update failed
-// and the container failed before the executor started, the executor
-// is properly killed and cleaned up.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_ExecutorCleanupWhenLaunchFailed)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("ls");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- // Fail the update so we don't proceed to send run task to the executor.
- EXPECT_CALL(dockerContainerizer, update(_, _))
- .WillRepeatedly(Return(Failure("Fail resource update")));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
-
- AWAIT_READY(statusFailed);
-
- EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
-
- driver.stop();
- driver.join();
-
- // We expect the executor to have exited, and if not in Shutdown
- // the test will fail because of the executor process still running.
- Shutdown();
-}
-
-
-// When the fetch fails we should send the scheduler a status
-// update with message the shows the actual error.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_FetchFailure)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("ls");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- EXPECT_CALL(*process, fetch(_, _))
- .WillOnce(Return(Failure("some error from fetch")));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
-
- AWAIT_READY(statusFailed);
-
- EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
- EXPECT_EQ("Failed to launch container: some error from fetch",
- statusFailed.get().message());
-
- // TODO(jaybuff): When MESOS-2035 is addressed we should validate
- // that statusFailed.get().reason() is correctly set here.
-
- driver.stop();
- driver.join();
-
- // We expect the executor to have exited, and if not in Shutdown
- // the test will fail because of the executor process still running.
- Shutdown();
-}
-
-
-// When the docker pull fails we should send the scheduler a status
-// update with message the shows the actual error.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_DockerPullFailure)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- CommandInfo command;
- command.set_value("ls");
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("busybox");
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
-
- task.mutable_command()->CopyFrom(command);
- task.mutable_container()->CopyFrom(containerInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(DoAll(FutureArg<0>(&containerId),
- Invoke(&dockerContainerizer,
- &MockDockerContainerizer::_launch)));
-
- EXPECT_CALL(*mockDocker, pull(_, _, _))
- .WillOnce(Return(Failure("some error from docker pull")));
-
- driver.launchTasks(offers.get()[0].id(), {task});
-
- AWAIT_READY_FOR(containerId, Seconds(60));
-
- AWAIT_READY(statusFailed);
-
- EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
- EXPECT_EQ("Failed to launch container: some error from docker pull",
- statusFailed.get().message());
-
- // TODO(jaybuff): When MESOS-2035 is addressed we should validate
- // that statusFailed.get().reason() is correctly set here.
-
- driver.stop();
- driver.join();
-
- // We expect the executor to have exited, and if not in Shutdown
- // the test will fail because of the executor process still running.
- Shutdown();
-}
-
-
-// When the docker executor container fails to launch, docker inspect
-// future that is in a retry loop should be discarded.
-TEST_F(DockerContainerizerTest, ROOT_DOCKER_DockerInspectDiscard)
-{
- Try<PID<Master>> master = StartMaster();
- ASSERT_SOME(master);
-
- slave::Flags flags = CreateSlaveFlags();
-
- MockDocker* mockDocker = new MockDocker(tests::flags.docker);
- Shared<Docker> docker(mockDocker);
-
- Future<Docker::Container> inspect;
- EXPECT_CALL(*mockDocker, inspect(_, _))
- .WillOnce(FutureResult(&inspect,
- Invoke((MockDocker*) docker.get(),
- &MockDocker::_inspect)));
-
- EXPECT_CALL(*mockDocker, run(_, _, _, _, _, _, _, _, _))
- .WillOnce(Return(Failure("Run failed")));
-
- Fetcher fetcher;
-
- // The docker containerizer will free the process, so we must
- // allocate on the heap.
- MockDockerContainerizerProcess* process =
- new MockDockerContainerizerProcess(flags, &fetcher, docker);
-
- MockDockerContainerizer dockerContainerizer(
- (Owned<DockerContainerizerProcess>(process)));
-
- Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
- ASSERT_SOME(slave);
-
- MockScheduler sched;
- MesosSchedulerDriver driver(
- &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
-
- Future<FrameworkID> frameworkId;
- EXPECT_CALL(sched, registered(&driver, _, _))
- .WillOnce(FutureArg<1>(&frameworkId));
-
- Future<vector<Offer>> offers;
- EXPECT_CALL(sched, resourceOffers(&driver, _))
- .WillOnce(FutureArg<1>(&offers))
- .WillRepeatedly(Return()); // Ignore subsequent offers.
-
- driver.start();
-
- AWAIT_READY(frameworkId);
-
- AWAIT_READY(offers);
- ASSERT_NE(0u, offers.get().size());
-
- const Offer& offer = offers.get()[0];
-
- TaskInfo task;
- task.set_name("");
- task.mutable_task_id()->set_value("1");
- task.mutable_slave_id()->CopyFrom(offer.slave_id());
- task.mutable_resources()->CopyFrom(offer.resources());
-
- ExecutorInfo executorInfo;
- ExecutorID executorId;
- executorId.set_value("e1");
- executorInfo.mutable_executor_id()->CopyFrom(executorId);
-
- CommandInfo command;
- command.set_value("/bin/test-executor");
- executorInfo.mutable_command()->CopyFrom(command);
-
- ContainerInfo containerInfo;
- containerInfo.set_type(ContainerInfo::DOCKER);
-
- // TODO(tnachen): Use local image to test if possible.
- ContainerInfo::DockerInfo dockerInfo;
- dockerInfo.set_image("tnachen/test-executor");
-
- containerInfo.mutable_docker()->CopyFrom(dockerInfo);
- executorInfo.mutable_container()->CopyFrom(containerInfo);
-
- task.mutable_executor()->CopyFrom(executorInfo);
-
- Future<TaskStatus> statusFailed;
- EXPECT_CALL(sched, statusUpdate(&driver, _))
- .WillOnce(FutureArg<1>(&statusFailed));
-
- Future<ContainerID> containerId;
- EXPECT
<TRUNCATED>
[02/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
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 {
[03/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/memory_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/memory_test_helper.cpp b/src/tests/memory_test_helper.cpp
deleted file mode 100644
index 8093e66..0000000
--- a/src/tests/memory_test_helper.cpp
+++ /dev/null
@@ -1,320 +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.n
- */
-
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <sys/mman.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include <string>
-#include <vector>
-
-#include <stout/bytes.hpp>
-#include <stout/error.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/lambda.hpp>
-#include <stout/os.hpp>
-#include <stout/stringify.hpp>
-#include <stout/strings.hpp>
-#include <stout/try.hpp>
-
-#include "tests/flags.hpp"
-#include "tests/memory_test_helper.hpp"
-
-using process::Subprocess;
-
-using std::cerr;
-using std::cin;
-using std::cout;
-using std::endl;
-using std::flush;
-using std::getline;
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-// Constants used to sync MemoryTestHelper and its subprocess.
-
-// Used by the subprocess to inform that it has started.
-const char STARTED = 'S';
-
-// Used by the subprocess to inform that the work requested is done.
-const char DONE = 'D';
-
-// Used to signal an increaseRSS request.
-const char INCREASE_RSS[] = "INCREASE_RSS";
-
-// Used to signal an increasePageCache request.
-const char INCREASE_PAGE_CACHE[] = "INCREASE_PAGE_CACHE";
-
-
-// This helper allocates and locks specified anonymous memory (RSS).
-// It uses mlock and memset to make sure allocated memory is mapped.
-static Try<void*> allocateRSS(const Bytes& size, bool lock = true)
-{
- void* rss = NULL;
-
- if (posix_memalign(&rss, getpagesize(), size.bytes()) != 0) {
- return ErrnoError("Failed to increase RSS memory, posix_memalign");
- }
-
- // Use memset to actually page in the memory in the kernel.
- memset(rss, 1, size.bytes());
-
- // Locking a page makes it unevictable in the kernel.
- if (lock && mlock(rss, size.bytes()) != 0) {
- return ErrnoError("Failed to lock memory, mlock");
- }
-
- return rss;
-}
-
-
-static Try<Nothing> increaseRSS(const vector<string>& tokens)
-{
- 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");
- }
-
- Try<void*> memory = allocateRSS(size.get());
- if (memory.isError()) {
- return Error("Failed to allocate RSS memory: " + memory.error());
- }
-
- return Nothing();
-}
-
-
-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 = strerror(errno);
-
- os::close(fd.get());
- return Error("Failed to fsync: " + message);
- }
- }
-
- os::close(fd.get());
- return Nothing();
-}
-
-
-MemoryTestHelper::~MemoryTestHelper()
-{
- cleanup();
-}
-
-
-Try<Nothing> MemoryTestHelper::spawn()
-{
- if (s.isSome()) {
- return Error("A subprocess has been spawned already");
- }
-
- vector<string> argv;
- argv.push_back("memory-test-helper");
- argv.push_back(MemoryTestHelperMain::NAME);
-
- Try<Subprocess> process = subprocess(
- path::join(flags.build_dir,
- "src",
- "memory-test-helper"),
- argv,
- Subprocess::PIPE(),
- Subprocess::PIPE(),
- Subprocess::FD(STDERR_FILENO));
-
- if (process.isError()) {
- return Error("Failed to spawn a subprocess: " + process.error());
- }
-
- s = process.get();
-
- // Wait for the child to inform it has started before returning.
- // Otherwise, the user might set the memory limit too earlier, and
- // cause the child oom-killed because 'ld' could use a lot of
- // memory.
- Result<string> read = os::read(s.get().out().get(), sizeof(STARTED));
- if (!read.isSome() || read.get() != string(sizeof(STARTED), STARTED)) {
- cleanup();
- return Error("Failed to sync with the subprocess");
- }
-
- return Nothing();
-}
-
-
-void MemoryTestHelper::cleanup()
-{
- if (s.isSome()) {
- // We just want to make sure the subprocess is terminated in case
- // it's stuck, but we don't care about its status. Any error
- // should have been logged in the subprocess directly.
- ::kill(s.get().pid(), SIGKILL);
- ::waitpid(s.get().pid(), NULL, 0);
- s = None();
- }
-}
-
-
-Try<pid_t> MemoryTestHelper::pid()
-{
- if (s.isNone()) {
- return Error("The subprocess has not been spawned yet");
- }
-
- return s.get().pid();
-}
-
-
-// Send a request to the subprocess and wait for its signal that the
-// work has been done.
-Try<Nothing> MemoryTestHelper::requestAndWait(const string& request)
-{
- if (s.isNone()) {
- return Error("The subprocess has not been spawned yet");
- }
-
- Try<Nothing> write = os::write(s.get().in().get(), request + "\n");
- if (write.isError()) {
- cleanup();
- return Error("Fail to sync with the subprocess: " + write.error());
- }
-
- Result<string> read = os::read(s.get().out().get(), sizeof(DONE));
- if (!read.isSome() || read.get() != string(sizeof(DONE), DONE)) {
- cleanup();
- return Error("Failed to sync with the subprocess");
- }
-
- return Nothing();
-}
-
-
-Try<Nothing> MemoryTestHelper::increaseRSS(const Bytes& size)
-{
- return requestAndWait(string(INCREASE_RSS) + " " + stringify(size));
-}
-
-
-Try<Nothing> MemoryTestHelper::increasePageCache(const Bytes& size)
-{
- return requestAndWait(string(INCREASE_PAGE_CACHE) + " " + stringify(size));
-}
-
-
-const char MemoryTestHelperMain::NAME[] = "MemoryTestHelperMain";
-
-
-int MemoryTestHelperMain::execute()
-{
- hashmap<string, Try<Nothing>(*)(const vector<string>&)> commands;
- commands[INCREASE_RSS] = &increaseRSS;
- commands[INCREASE_PAGE_CACHE] = &increasePageCache;
-
- // Tell the parent that child has started.
- cout << STARTED << flush;
-
- string line;
- while(cin.good()) {
- getline(cin, line);
- vector<string> tokens = strings::tokenize(line, " ");
-
- if (tokens.empty()) {
- cerr << "No command from the parent" << endl;
- return 1;
- }
-
- if (!commands.contains(tokens[0])) {
- cerr << "Unknown command from the parent '" << tokens[0] << "'" << endl;
- return 1;
- }
-
- Try<Nothing> result = commands[tokens[0]](tokens);
- if (result.isError()) {
- cerr << result.error();
- return 1;
- }
-
- cout << DONE << flush;
- }
-
- if (!cin) {
- cerr << "Failed to sync with the parent" << endl;
- return 1;
- }
-
- return 0;
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/memory_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/memory_test_helper.hpp b/src/tests/memory_test_helper.hpp
deleted file mode 100644
index 11712d7..0000000
--- a/src/tests/memory_test_helper.hpp
+++ /dev/null
@@ -1,89 +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.
- */
-
-#ifndef __MEMORY_TEST_HELPER_HPP__
-#define __MEMORY_TEST_HELPER_HPP__
-
-#include <process/subprocess.hpp>
-
-#include <stout/bytes.hpp>
-#include <stout/option.hpp>
-#include <stout/subcommand.hpp>
-#include <stout/try.hpp>
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-// The abstraction for controlling the memory usage of a subprocess.
-// TODO(chzhcn): Currently, this helper is only supposed to be used by
-// one thread. Consider making it thread safe.
-class MemoryTestHelper
-{
-public:
- MemoryTestHelper() {};
- ~MemoryTestHelper();
-
- // Spawns a subprocess.
- // TODO(chzhcn): Consider returning a future instead of blocking.
- Try<Nothing> spawn();
-
- // Kill and reap the subprocess if exists.
- // TODO(chzhcn): Consider returning a future instead of blocking.
- void cleanup();
-
- // Returns the pid of the subprocess.
- Try<pid_t> pid();
-
- // Allocate and lock specified page-aligned anonymous memory (RSS)
- // in the subprocess. It uses mlock and memset to make sure
- // allocated memory is mapped.
- // TODO(chzhcn): Consider returning a future instead of blocking.
- Try<Nothing> increaseRSS(const Bytes& size);
-
- // This function attempts to generate requested size of page cache
- // in the subprocess by using a small buffer and writing it to disk
- // multiple times.
- // TODO(chzhcn): Consider returning a future instead of blocking.
- Try<Nothing> increasePageCache(const Bytes& size = Megabytes(1));
-
-private:
- Try<Nothing> requestAndWait(const std::string& request);
-
- Option<process::Subprocess> s;
-};
-
-
-// The actual subprocess behind MemoryTestHelper. It runs in a loop
-// and executes commands passed from stdin.
-class MemoryTestHelperMain : public Subcommand
-{
-public:
- static const char NAME[];
-
- MemoryTestHelperMain() : Subcommand(NAME) {};
-
-protected:
- virtual int execute();
-};
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
-
-#endif // __MEMORY_TEST_HELPER_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/memory_test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/memory_test_helper_main.cpp b/src/tests/memory_test_helper_main.cpp
deleted file mode 100644
index 362535f..0000000
--- a/src/tests/memory_test_helper_main.cpp
+++ /dev/null
@@ -1,32 +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 <stout/subcommand.hpp>
-
-#include "tests/memory_test_helper.hpp"
-
-using mesos::internal::tests::MemoryTestHelperMain;
-
-int main(int argc, char** argv)
-{
- return Subcommand::dispatch(
- None(),
- argc,
- argv,
- new MemoryTestHelperMain());
-}
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/ns_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/ns_tests.cpp b/src/tests/ns_tests.cpp
deleted file mode 100644
index bcd0e12..0000000
--- a/src/tests/ns_tests.cpp
+++ /dev/null
@@ -1,301 +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 <sys/wait.h>
-
-#include <iostream>
-
-#include <pthread.h>
-#include <unistd.h>
-
-#include <list>
-#include <set>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-#include <stout/gtest.hpp>
-#include <stout/lambda.hpp>
-#include <stout/os.hpp>
-
-#include <process/gtest.hpp>
-#include <process/subprocess.hpp>
-
-#include "linux/ns.hpp"
-
-#include "tests/flags.hpp"
-#include "tests/setns_test_helper.hpp"
-
-using namespace process;
-
-using std::list;
-using std::set;
-using std::string;
-using std::vector;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-// Helper for cloneChild() which expects an int(void*).
-static int cloneChildHelper(void* _func)
-{
- const lambda::function<int()>* func =
- static_cast<const lambda::function<int()>*> (_func);
-
- return (*func)();
-}
-
-
-static pid_t cloneChild(
- int flags,
- const lambda::function<int()>& func)
-
-{
- // 8 MiB stack for child.
- static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
-
- return ::clone(
- cloneChildHelper,
- &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
- flags | SIGCHLD,
- (void*) &func);
-}
-
-
-// Test that a child in different namespace(s) can setns back to the
-// root namespace. We must fork a child to test this because setns
-// doesn't support multi-threaded processes (which gtest is).
-TEST(NsTest, ROOT_setns)
-{
- // Clone then exec the setns-test-helper into a new namespace for
- // each available namespace.
- set<string> namespaces = ns::namespaces();
- ASSERT_FALSE(namespaces.empty());
-
- int flags = 0;
-
- foreach (const string& ns, namespaces) {
- // Skip 'user' namespace because it causes 'clone' to change us
- // from being user 'root' to user 'nobody', but these tests
- // require root. See MESOS-3083.
- if (ns == "user") {
- continue;
- }
-
- Try<int> nstype = ns::nstype(ns);
- ASSERT_SOME(nstype);
-
- flags |= nstype.get();
- }
-
- vector<string> argv;
- argv.push_back("setns-test-helper");
- argv.push_back(SetnsTestHelper::NAME);
-
- Try<Subprocess> s = subprocess(
- path::join(tests::flags.build_dir, "src", "setns-test-helper"),
- argv,
- Subprocess::FD(STDIN_FILENO),
- Subprocess::FD(STDOUT_FILENO),
- Subprocess::FD(STDERR_FILENO),
- None(),
- None(),
- None(),
- lambda::bind(&cloneChild, flags, lambda::_1));
-
- // Continue in parent.
- ASSERT_SOME(s);
-
- // The child should exit 0.
- Future<Option<int>> status = s.get().status();
- AWAIT_READY(status);
-
- ASSERT_SOME(status.get());
- EXPECT_TRUE(WIFEXITED(status.get().get()));
- EXPECT_EQ(0, status.get().get());
-}
-
-
-static void* childThread(void* arg)
-{
- // Newly created threads have PTHREAD_CANCEL_ENABLE and
- // PTHREAD_CANCEL_DEFERRED so they can be cancelled.
- while (true) { os::sleep(Seconds(1)); }
-
- return NULL;
-}
-
-
-// Test that setns correctly refuses to re-associate to a namespace if
-// the caller is multi-threaded.
-TEST(NsTest, ROOT_setnsMultipleThreads)
-{
- set<string> namespaces = ns::namespaces();
- EXPECT_LT(0u, namespaces.size());
-
- // Do not allow multi-threaded environment.
- pthread_t pthread;
- ASSERT_EQ(0, pthread_create(&pthread, NULL, childThread, NULL));
-
- foreach (const string& ns, namespaces) {
- EXPECT_ERROR(ns::setns(::getpid(), ns));
- }
-
- // Terminate the threads.
- EXPECT_EQ(0, pthread_cancel(pthread));
- EXPECT_EQ(0, pthread_join(pthread, NULL));
-}
-
-
-// Use a different child function for clone because it requires
-// int(*)(void*).
-static int childGetns(void* arg)
-{
- // Sleep until killed.
- while (true) { sleep(1); }
-
- ABORT("Error, child should be killed before reaching here");
-}
-
-
-// Test that we can get the namespace inodes for a forked child.
-TEST(NsTest, ROOT_getns)
-{
- set<string> namespaces = ns::namespaces();
-
- // ns::setns() does not support "pid".
- namespaces.erase("pid");
-
- // Use the first other namespace available.
- ASSERT_FALSE(namespaces.empty());
- string ns = *(namespaces.begin());
-
- ASSERT_SOME(ns::getns(::getpid(), ns));
-
- Try<int> nstype = ns::nstype(ns);
- ASSERT_SOME(nstype);
-
- // 8 MiB stack for child.
- static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
-
- pid_t pid = clone(
- childGetns,
- &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
- SIGCHLD | nstype.get(),
- NULL);
-
- ASSERT_NE(-1, pid);
-
- // Continue in parent.
- Try<ino_t> nsParent = ns::getns(::getpid(), ns);
- ASSERT_SOME(nsParent);
-
- Try<ino_t> nsChild = ns::getns(pid, ns);
- ASSERT_SOME(nsChild);
-
- // Child should be in a different namespace.
- EXPECT_NE(nsParent.get(), nsChild.get());
-
- // Kill the child process.
- ASSERT_NE(-1, ::kill(pid, SIGKILL));
-
- // Wait for the child process.
- int status;
- EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-
-static int childDestroy(void* arg)
-{
- // Fork a bunch of children.
- ::fork();
- ::fork();
- ::fork();
-
- // Parent and all children sleep.
- while (true) { sleep(1); }
-
- ABORT("Error, child should be killed before reaching here");
-}
-
-
-// Test we can destroy a pid namespace, i.e., kill all processes.
-TEST(NsTest, ROOT_destroy)
-{
- set<string> namespaces = ns::namespaces();
-
- if (namespaces.count("pid") == 0) {
- // Pid namespace is not available.
- return;
- }
-
- Try<int> nstype = ns::nstype("pid");
- ASSERT_SOME(nstype);
-
- // 8 MiB stack for child.
- static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
-
- pid_t pid = clone(
- childDestroy,
- &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
- SIGCHLD | nstype.get(),
- NULL);
-
- ASSERT_NE(-1, pid);
-
- Future<Option<int>> status = process::reap(pid);
-
- // Ensure the child is in a different pid namespace.
- Try<ino_t> childNs = ns::getns(pid, "pid");
- ASSERT_SOME(childNs);
-
- Try<ino_t> ourNs = ns::getns(::getpid(), "pid");
- ASSERT_SOME(ourNs);
-
- ASSERT_NE(ourNs.get(), childNs.get());
-
- // Kill the child.
- AWAIT_READY(ns::pid::destroy(childNs.get()));
-
- AWAIT_READY(status);
- ASSERT_SOME(status.get());
- ASSERT_TRUE(WIFSIGNALED(status.get().get()));
- EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
-
- // Finally, verify that no processes are in the child's pid
- // namespace, i.e., destroy() also killed all descendants.
- Try<set<pid_t>> pids = os::pids();
- ASSERT_SOME(pids);
-
- foreach (pid_t pid, pids.get()) {
- Try<ino_t> otherNs = ns::getns(pid, "pid");
- // pid may have exited since getting the snapshot of pids so
- // ignore any error.
- if (otherNs.isSome()) {
- ASSERT_SOME_NE(childNs.get(), otherNs);
- }
- }
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/perf_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/perf_tests.cpp b/src/tests/perf_tests.cpp
deleted file mode 100644
index 6b3d70f..0000000
--- a/src/tests/perf_tests.cpp
+++ /dev/null
@@ -1,183 +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 <sys/prctl.h>
-
-#include <set>
-
-#include <gmock/gmock.h>
-
-#include <process/clock.hpp>
-#include <process/gtest.hpp>
-#include <process/reap.hpp>
-
-#include <stout/gtest.hpp>
-#include <stout/stringify.hpp>
-
-#include "linux/perf.hpp"
-
-using std::set;
-using std::string;
-
-using namespace process;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class PerfTest : public ::testing::Test {};
-
-
-TEST_F(PerfTest, ROOT_Events)
-{
- set<string> events;
- // Valid events.
- events.insert("cycles");
- events.insert("task-clock");
- EXPECT_TRUE(perf::valid(events));
-
- // Add an invalid event.
- events.insert("this-is-an-invalid-event");
- EXPECT_FALSE(perf::valid(events));
-}
-
-
-TEST_F(PerfTest, Parse)
-{
- // uint64 and floats should be parsed.
- Try<hashmap<string, mesos::PerfStatistics> > parse =
- perf::parse("123,cycles\n0.123,task-clock");
- CHECK_SOME(parse);
-
- ASSERT_TRUE(parse.get().contains(""));
- mesos::PerfStatistics statistics = parse.get().get("").get();
-
- ASSERT_TRUE(statistics.has_cycles());
- EXPECT_EQ(123u, statistics.cycles());
- ASSERT_TRUE(statistics.has_task_clock());
- EXPECT_EQ(0.123, statistics.task_clock());
-
- // Parse multiple cgroups.
- parse = perf::parse("123,cycles,cgroup1\n"
- "456,cycles,cgroup2\n"
- "0.456,task-clock,cgroup2\n"
- "0.123,task-clock,cgroup1");
- CHECK_SOME(parse);
- EXPECT_FALSE(parse.get().contains(""));
-
- ASSERT_TRUE(parse.get().contains("cgroup1"));
- statistics = parse.get().get("cgroup1").get();
-
- ASSERT_TRUE(statistics.has_cycles());
- EXPECT_EQ(123u, statistics.cycles());
- ASSERT_TRUE(statistics.has_task_clock());
- EXPECT_EQ(0.123, statistics.task_clock());
-
- ASSERT_TRUE(parse.get().contains("cgroup2"));
- statistics = parse.get().get("cgroup2").get();
-
- ASSERT_TRUE(statistics.has_cycles());
- EXPECT_EQ(456u, statistics.cycles());
- EXPECT_TRUE(statistics.has_task_clock());
- EXPECT_EQ(0.456, statistics.task_clock());
-
- // Statistics reporting <not supported> should not appear.
- parse = perf::parse("<not supported>,cycles");
- CHECK_SOME(parse);
-
- ASSERT_TRUE(parse.get().contains(""));
- statistics = parse.get().get("").get();
- EXPECT_FALSE(statistics.has_cycles());
-
- // Statistics reporting <not counted> should be zero.
- parse = perf::parse("<not counted>,cycles\n<not counted>,task-clock");
- CHECK_SOME(parse);
-
- ASSERT_TRUE(parse.get().contains(""));
- statistics = parse.get().get("").get();
-
- EXPECT_TRUE(statistics.has_cycles());
- EXPECT_EQ(0u, statistics.cycles());
- EXPECT_TRUE(statistics.has_task_clock());
- EXPECT_EQ(0.0, statistics.task_clock());
-
- // Check parsing fails.
- parse = perf::parse("1,cycles\ngarbage");
- EXPECT_ERROR(parse);
-
- parse = perf::parse("1,unknown-field");
- EXPECT_ERROR(parse);
-}
-
-
-TEST_F(PerfTest, ROOT_SamplePid)
-{
- // TODO(idownes): Replace this with a Subprocess when it supports
- // DEATHSIG.
- // Fork a child which we'll run perf against.
- pid_t pid = fork();
- ASSERT_GE(pid, 0);
-
- if (pid == 0) {
- // Kill ourself if the parent dies to prevent leaking the child.
- prctl(PR_SET_PDEATHSIG, SIGKILL);
-
- // Spin child to consume cpu cycles.
- while (true);
- }
-
- // Continue in parent.
- set<string> events;
- // Hardware event.
- events.insert("cycles");
- // Software event.
- events.insert("task-clock");
-
- // Sample the child.
- Duration duration = Milliseconds(100);
- Future<mesos::PerfStatistics> statistics =
- perf::sample(events, pid, duration);
- AWAIT_READY(statistics);
-
- // Kill the child and reap it.
- Future<Option<int>> status = reap(pid);
- kill(pid, SIGKILL);
- AWAIT_READY(status);
-
- // Check the sample timestamp is within the last 5 seconds. This is generous
- // because there's the process reap delay in addition to the sampling
- // duration.
- ASSERT_TRUE(statistics.get().has_timestamp());
- EXPECT_GT(
- Seconds(5).secs(), Clock::now().secs() - statistics.get().timestamp());
- EXPECT_EQ(duration.secs(), statistics.get().duration());
-
- ASSERT_TRUE(statistics.get().has_cycles());
-
- // TODO(benh): Some Linux distributions (Ubuntu 14.04) fail to
- // properly sample 'cycles' with 'perf', so we don't explicitly
- // check the value here. See MESOS-3082.
- // EXPECT_LT(0u, statistics.get().cycles());
-
- ASSERT_TRUE(statistics.get().has_task_clock());
- EXPECT_LT(0.0, statistics.get().task_clock());
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
[11/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/cgroups_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/cgroups_tests.cpp b/src/tests/containerizer/cgroups_tests.cpp
new file mode 100644
index 0000000..caecd5d
--- /dev/null
+++ b/src/tests/containerizer/cgroups_tests.cpp
@@ -0,0 +1,1235 @@
+/**
+ * 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 <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <sys/mman.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <gmock/gmock.h>
+
+#include <process/gtest.hpp>
+#include <process/owned.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/numify.hpp>
+#include <stout/option.hpp>
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+#include <stout/proc.hpp>
+#include <stout/stringify.hpp>
+#include <stout/strings.hpp>
+
+#include "linux/cgroups.hpp"
+#include "linux/perf.hpp"
+
+#include "tests/mesos.hpp" // For TEST_CGROUPS_(HIERARCHY|ROOT).
+#include "tests/utils.hpp"
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using namespace process;
+
+using cgroups::memory::pressure::Level;
+using cgroups::memory::pressure::Counter;
+
+using std::set;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+class CgroupsTest : public TemporaryDirectoryTest
+{
+public:
+ static void SetUpTestCase()
+ {
+ // Clean up the testing hierarchy, in case it wasn't cleaned up
+ // properly from previous tests.
+ AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
+ }
+
+ static void TearDownTestCase()
+ {
+ AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
+ }
+};
+
+
+// A fixture which is used to name tests that expect NO hierarchy to
+// exist in order to test the ability to create a hierarchy (since
+// most likely existing hierarchies will have all or most subsystems
+// attached rendering our ability to create a hierarchy fruitless).
+class CgroupsNoHierarchyTest : public CgroupsTest
+{
+public:
+ static void SetUpTestCase()
+ {
+ CgroupsTest::SetUpTestCase();
+
+ Try<std::set<std::string> > hierarchies = cgroups::hierarchies();
+ ASSERT_SOME(hierarchies);
+ ASSERT_TRUE(hierarchies.get().empty())
+ << "-------------------------------------------------------------\n"
+ << "We cannot run any cgroups tests that require mounting\n"
+ << "hierarchies because you have the following hierarchies mounted:\n"
+ << strings::trim(stringify(hierarchies.get()), " {},") << "\n"
+ << "You can either unmount those hierarchies, or disable\n"
+ << "this test case (i.e., --gtest_filter=-CgroupsNoHierarchyTest.*).\n"
+ << "-------------------------------------------------------------";
+ }
+};
+
+
+// A fixture that assumes ANY hierarchy is acceptable for use provided
+// it has the subsystems attached that were specified in the
+// constructor. If no hierarchy could be found that has all the
+// required subsystems then we attempt to create a new hierarchy.
+class CgroupsAnyHierarchyTest : public CgroupsTest
+{
+public:
+ CgroupsAnyHierarchyTest(const std::string& _subsystems = "cpu")
+ : subsystems(_subsystems) {}
+
+protected:
+ virtual void SetUp()
+ {
+ CgroupsTest::SetUp();
+
+ foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
+ // Establish the base hierarchy if this is the first subsystem checked.
+ if (baseHierarchy.empty()) {
+ Result<std::string> hierarchy = cgroups::hierarchy(subsystem);
+ ASSERT_FALSE(hierarchy.isError());
+
+ if (hierarchy.isNone()) {
+ baseHierarchy = TEST_CGROUPS_HIERARCHY;
+ } else {
+ // Strip the subsystem to get the base hierarchy.
+ Try<std::string> baseDirname = Path(hierarchy.get()).dirname();
+ ASSERT_SOME(baseDirname);
+ baseHierarchy = baseDirname.get();
+ }
+ }
+
+ // Mount the subsystem if necessary.
+ std::string hierarchy = path::join(baseHierarchy, subsystem);
+ Try<bool> mounted = cgroups::mounted(hierarchy, subsystem);
+ ASSERT_SOME(mounted);
+ if (!mounted.get()) {
+ ASSERT_SOME(cgroups::mount(hierarchy, subsystem))
+ << "-------------------------------------------------------------\n"
+ << "We cannot run any cgroups tests that require\n"
+ << "a hierarchy with subsystem '" << subsystem << "'\n"
+ << "because we failed to find an existing hierarchy\n"
+ << "or create a new one (tried '" << hierarchy << "').\n"
+ << "You can either remove all existing\n"
+ << "hierarchies, or disable this test case\n"
+ << "(i.e., --gtest_filter=-"
+ << ::testing::UnitTest::GetInstance()
+ ->current_test_info()
+ ->test_case_name() << ".*).\n"
+ << "-------------------------------------------------------------";
+ }
+
+ Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
+ CHECK_SOME(cgroups);
+
+ foreach (const std::string& cgroup, cgroups.get()) {
+ // Remove any cgroups that start with TEST_CGROUPS_ROOT.
+ if (cgroup == TEST_CGROUPS_ROOT) {
+ AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
+ }
+ }
+ }
+ }
+
+ virtual void TearDown()
+ {
+ // Remove all *our* cgroups.
+ foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
+ std::string hierarchy = path::join(baseHierarchy, subsystem);
+
+ Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
+ CHECK_SOME(cgroups);
+
+ foreach (const std::string& cgroup, cgroups.get()) {
+ // Remove any cgroups that start with TEST_CGROUPS_ROOT.
+ if (cgroup == TEST_CGROUPS_ROOT) {
+ AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
+ }
+ }
+ }
+
+ CgroupsTest::TearDown();
+ }
+
+ const std::string subsystems; // Subsystems required to run tests.
+ std::string baseHierarchy; // Path to the hierarchy being used.
+};
+
+
+class CgroupsAnyHierarchyWithCpuMemoryTest
+ : public CgroupsAnyHierarchyTest
+{
+public:
+ CgroupsAnyHierarchyWithCpuMemoryTest()
+ : CgroupsAnyHierarchyTest("cpu,memory") {}
+};
+
+
+class CgroupsAnyHierarchyWithFreezerTest
+ : public CgroupsAnyHierarchyTest
+{
+public:
+ CgroupsAnyHierarchyWithFreezerTest()
+ : CgroupsAnyHierarchyTest("freezer") {}
+};
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Enabled)
+{
+ EXPECT_SOME_TRUE(cgroups::enabled(""));
+ EXPECT_SOME_TRUE(cgroups::enabled(","));
+ EXPECT_SOME_TRUE(cgroups::enabled("cpu"));
+ EXPECT_SOME_TRUE(cgroups::enabled(",cpu"));
+ EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory"));
+ EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory,"));
+ EXPECT_ERROR(cgroups::enabled("invalid"));
+ EXPECT_ERROR(cgroups::enabled("cpu,invalid"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Busy)
+{
+ EXPECT_SOME_FALSE(cgroups::busy(""));
+ EXPECT_SOME_FALSE(cgroups::busy(","));
+ EXPECT_SOME_TRUE(cgroups::busy("cpu"));
+ EXPECT_SOME_TRUE(cgroups::busy(",cpu"));
+ EXPECT_SOME_TRUE(cgroups::busy("cpu,memory"));
+ EXPECT_SOME_TRUE(cgroups::busy("cpu,memory,"));
+ EXPECT_ERROR(cgroups::busy("invalid"));
+ EXPECT_ERROR(cgroups::busy("cpu,invalid"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Subsystems)
+{
+ Try<std::set<std::string> > names = cgroups::subsystems();
+ ASSERT_SOME(names);
+
+ Option<std::string> cpu;
+ Option<std::string> memory;
+ foreach (const std::string& name, names.get()) {
+ if (name == "cpu") {
+ cpu = name;
+ } else if (name == "memory") {
+ memory = name;
+ }
+ }
+
+ EXPECT_SOME(cpu);
+ EXPECT_SOME(memory);
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_SubsystemsHierarchy)
+{
+ std::string cpuHierarchy = path::join(baseHierarchy, "cpu");
+
+ Try<std::set<std::string> > names = cgroups::subsystems(cpuHierarchy);
+ ASSERT_SOME(names);
+
+ Option<std::string> cpu;
+ Option<std::string> memory;
+ foreach (const std::string& name, names.get()) {
+ if (name == "cpu") {
+ cpu = name;
+ } else if (name == "memory") {
+ memory = name;
+ }
+ }
+
+ EXPECT_SOME(cpu);
+ EXPECT_NONE(memory);
+
+ std::string memoryHierarchy = path::join(baseHierarchy, "memory");
+ names = cgroups::subsystems(memoryHierarchy);
+ ASSERT_SOME(names);
+
+ cpu = None();
+ memory = None();
+ foreach (const std::string& name, names.get()) {
+ if (name == "cpu") {
+ cpu = name;
+ } else if (name == "memory") {
+ memory = name;
+ }
+ }
+ EXPECT_NONE(cpu);
+ EXPECT_SOME(memory);
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FindCgroupSubsystems)
+{
+ pid_t pid = ::getpid();
+ Result<std::string> cpuHierarchy = cgroups::cpu::cgroup(pid);
+ EXPECT_FALSE(cpuHierarchy.isError());
+ EXPECT_SOME(cpuHierarchy);
+
+ Result<std::string> memHierarchy = cgroups::memory::cgroup(pid);
+ EXPECT_FALSE(memHierarchy.isError());
+ EXPECT_SOME(memHierarchy);
+}
+
+
+TEST_F(CgroupsNoHierarchyTest, ROOT_CGROUPS_NOHIERARCHY_MountUnmountHierarchy)
+{
+ EXPECT_ERROR(cgroups::mount("/tmp", "cpu"));
+ EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "invalid"));
+
+ // Try to mount a valid hierarchy, retrying as necessary since the
+ // previous unmount might not have taken effect yet due to a bug in
+ // Ubuntu 12.04.
+ ASSERT_SOME(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpu,memory", 10));
+ EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpuset"));
+ EXPECT_ERROR(cgroups::unmount("/tmp"));
+ ASSERT_SOME(cgroups::unmount(TEST_CGROUPS_HIERARCHY));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Mounted)
+{
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist"));
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp"));
+ EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected"));
+ EXPECT_SOME_TRUE(cgroups::mounted(baseHierarchy + "/cpu"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_MountedSubsystems)
+{
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist", "cpu"));
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu,memory"));
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu"));
+ EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "invalid"));
+ EXPECT_SOME_TRUE(cgroups::mounted(path::join(baseHierarchy, "cpu"), "cpu"));
+ EXPECT_SOME_TRUE(cgroups::mounted(
+ path::join(baseHierarchy, "memory"), "memory"));
+ EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy, "invalid"));
+ EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected", "cpu"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_CreateRemove)
+{
+ EXPECT_ERROR(cgroups::create("/tmp", "test"));
+ EXPECT_ERROR(cgroups::create(baseHierarchy, "mesos_test_missing/1"));
+ ASSERT_SOME(cgroups::create(
+ path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
+ EXPECT_ERROR(cgroups::remove(baseHierarchy, "invalid"));
+ ASSERT_SOME(cgroups::remove(
+ path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Get)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+
+ ASSERT_SOME(cgroups::create(hierarchy, "mesos_test1"));
+ ASSERT_SOME(cgroups::create(hierarchy, "mesos_test2"));
+
+ Try<std::vector<std::string>> cgroups = cgroups::get(hierarchy);
+ ASSERT_SOME(cgroups);
+
+ EXPECT_NE(cgroups.get().end(),
+ find(cgroups.get().begin(), cgroups.get().end(), "mesos_test2"));
+ EXPECT_NE(cgroups.get().end(),
+ find(cgroups.get().begin(), cgroups.get().end(), "mesos_test1"));
+
+ ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test1"));
+ ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test2"));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_NestedCgroups)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+ std::string cgroup1 = path::join(TEST_CGROUPS_ROOT, "1");
+ std::string cgroup2 = path::join(TEST_CGROUPS_ROOT, "2");
+
+ ASSERT_SOME(cgroups::create(hierarchy, cgroup1))
+ << "-------------------------------------------------------------\n"
+ << "We cannot run this test because it appears you do not have\n"
+ << "a modern enough version of the Linux kernel. You won't be\n"
+ << "able to use the cgroups isolator, but feel free to disable\n"
+ << "this test.\n"
+ << "-------------------------------------------------------------";
+
+ ASSERT_SOME(cgroups::create(hierarchy, cgroup2));
+
+ Try<std::vector<std::string>> cgroups =
+ cgroups::get(hierarchy, TEST_CGROUPS_ROOT);
+ ASSERT_SOME(cgroups);
+
+ ASSERT_EQ(2u, cgroups.get().size());
+
+ EXPECT_NE(cgroups.get().end(),
+ find(cgroups.get().begin(), cgroups.get().end(), cgroup2));
+ EXPECT_NE(cgroups.get().end(),
+ find(cgroups.get().begin(), cgroups.get().end(), cgroup1));
+
+ ASSERT_SOME(cgroups::remove(hierarchy, cgroup1));
+ ASSERT_SOME(cgroups::remove(hierarchy, cgroup2));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Tasks)
+{
+ pid_t pid = ::getpid();
+
+ Result<std::string> cgroup = cgroups::cpu::cgroup(pid);
+ ASSERT_SOME(cgroup);
+
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+
+ Try<std::set<pid_t>> pids = cgroups::processes(hierarchy, cgroup.get());
+ ASSERT_SOME(pids);
+
+ EXPECT_NE(0u, pids.get().count(pid));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Read)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+
+ EXPECT_ERROR(cgroups::read(hierarchy, TEST_CGROUPS_ROOT, "invalid42"));
+
+ pid_t pid = ::getpid();
+
+ Result<std::string> cgroup = cgroups::cpu::cgroup(pid);
+ ASSERT_SOME(cgroup);
+
+ Try<std::string> read = cgroups::read(hierarchy, cgroup.get(), "tasks");
+ ASSERT_SOME(read);
+
+ EXPECT_TRUE(strings::contains(read.get(), stringify(pid)));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Write)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+ EXPECT_ERROR(
+ cgroups::write(hierarchy, TEST_CGROUPS_ROOT, "invalid", "invalid"));
+
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // In child process, wait for kill signal.
+ while (true) { sleep(1); }
+
+ // Should not reach here.
+ const char* message = "Error, child should be killed before reaching here";
+ while (write(STDERR_FILENO, message, strlen(message)) == -1 &&
+ errno == EINTR);
+
+ _exit(1);
+ }
+
+ // In parent process.
+ ASSERT_SOME(
+ cgroups::write(hierarchy,
+ TEST_CGROUPS_ROOT,
+ "cgroup.procs",
+ stringify(pid)));
+
+ Try<std::set<pid_t> > pids = cgroups::processes(hierarchy, TEST_CGROUPS_ROOT);
+ ASSERT_SOME(pids);
+
+ EXPECT_NE(0u, pids.get().count(pid));
+
+ // Kill the child process.
+ ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ int status;
+ EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+
+TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Cfs_Big_Quota)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ Duration quota = Seconds(100); // Big quota.
+ ASSERT_SOME(cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT, quota));
+
+ // Ensure we can read back the correct quota.
+ ASSERT_SOME_EQ(
+ quota,
+ cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT));
+}
+
+
+class CgroupsAnyHierarchyWithCpuAcctMemoryTest
+ : public CgroupsAnyHierarchyTest
+{
+public:
+ CgroupsAnyHierarchyWithCpuAcctMemoryTest()
+ : CgroupsAnyHierarchyTest("cpuacct,memory") {}
+};
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_Stat)
+{
+ EXPECT_ERROR(cgroups::stat(baseHierarchy, TEST_CGROUPS_ROOT, "invalid"));
+
+ Try<hashmap<std::string, uint64_t> > result =
+ cgroups::stat(
+ path::join(baseHierarchy, "cpuacct"), "/", "cpuacct.stat");
+ ASSERT_SOME(result);
+ EXPECT_TRUE(result.get().contains("user"));
+ EXPECT_TRUE(result.get().contains("system"));
+ EXPECT_GT(result.get().get("user").get(), 0llu);
+ EXPECT_GT(result.get().get("system").get(), 0llu);
+
+ result = cgroups::stat(
+ path::join(baseHierarchy, "memory"), "/", "memory.stat");
+ ASSERT_SOME(result);
+ EXPECT_TRUE(result.get().contains("rss"));
+ EXPECT_GT(result.get().get("rss").get(), 0llu);
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Listen)
+{
+ std::string hierarchy = path::join(baseHierarchy, "memory");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+ ASSERT_SOME(
+ cgroups::memory::oom::killer::enabled(hierarchy, TEST_CGROUPS_ROOT))
+ << "-------------------------------------------------------------\n"
+ << "We cannot run this test because it appears you do not have\n"
+ << "a modern enough version of the Linux kernel. You won't be\n"
+ << "able to use the cgroups isolator, but feel free to disable\n"
+ << "this test.\n"
+ << "-------------------------------------------------------------";
+
+ const Bytes limit = Megabytes(64);
+
+ ASSERT_SOME(cgroups::memory::limit_in_bytes(
+ hierarchy, TEST_CGROUPS_ROOT, limit));
+
+ // Listen on oom events for test cgroup.
+ Future<Nothing> future =
+ cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
+
+ ASSERT_FALSE(future.isFailed());
+
+ // Test the cancellation.
+ future.discard();
+
+ // Test the normal operation below.
+ future = cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
+ ASSERT_FALSE(future.isFailed());
+
+ MemoryTestHelper helper;
+ ASSERT_SOME(helper.spawn());
+ ASSERT_SOME(helper.pid());
+
+ EXPECT_SOME(cgroups::assign(
+ hierarchy, TEST_CGROUPS_ROOT, helper.pid().get()));
+
+ // Request more RSS memory in the subprocess than the limit.
+ // NOTE: We enable the kernel oom killer in this test. If it were
+ // disabled, the subprocess might hang and the following call won't
+ // return. By enabling the oom killer, we let the subprocess get
+ // killed and expect that an error is returned.
+ EXPECT_ERROR(helper.increaseRSS(limit * 2));
+
+ AWAIT_READY(future);
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Freeze)
+{
+ int pipes[2];
+ int dummy;
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // In child process.
+ ::close(pipes[0]);
+
+ // Put self into the test cgroup.
+ Try<Nothing> assign =
+ cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
+
+ if (assign.isError()) {
+ std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
+ abort();
+ }
+
+ // Notify the parent.
+ if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
+ perror("Failed to notify the parent");
+ abort();
+ }
+ ::close(pipes[1]);
+
+ // Infinite loop here.
+ while (true);
+
+ // Should not reach here.
+ std::cerr << "Reach an unreachable statement!" << std::endl;
+ abort();
+ }
+
+ // In parent process.
+ ::close(pipes[1]);
+
+ // Wait until child has assigned the cgroup.
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ::close(pipes[0]);
+
+ // Freeze the test cgroup.
+ AWAIT_EXPECT_READY(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
+
+ // Thaw the test cgroup.
+ AWAIT_EXPECT_READY(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
+
+ // Kill the child process.
+ ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ int status;
+ EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FreezeNonFreezer)
+{
+ std::string hierarchy = path::join(baseHierarchy, "cpu");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ AWAIT_EXPECT_FAILED(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
+ AWAIT_EXPECT_FAILED(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
+
+ // The cgroup is empty so we should still be able to destroy it.
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Kill)
+{
+ int pipes[2];
+ int dummy;
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid > 0) {
+ // In parent process.
+ ::close(pipes[1]);
+
+ // Wait until all children have assigned the cgroup.
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ::close(pipes[0]);
+
+ Try<Nothing> kill = cgroups::kill(hierarchy, TEST_CGROUPS_ROOT, SIGKILL);
+ EXPECT_SOME(kill);
+
+ int status;
+ EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+ } else {
+ // In child process.
+
+ // We create 4 child processes here using two forks to test the case in
+ // which there are multiple active processes in the given cgroup.
+ ::fork();
+ ::fork();
+
+ // Put self into the test cgroup.
+ Try<Nothing> assign =
+ cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
+
+ if (assign.isError()) {
+ std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
+ abort();
+ }
+
+ // Notify the parent.
+ ::close(pipes[0]); // TODO(benh): Close after first fork?
+ if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
+ perror("Failed to notify the parent");
+ abort();
+ }
+ ::close(pipes[1]);
+
+ // Wait kill signal from parent.
+ while (true);
+
+ // Should not reach here.
+ std::cerr << "Reach an unreachable statement!" << std::endl;
+ abort();
+ }
+}
+
+
+// TODO(benh): Write a version of this test with nested cgroups.
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Destroy)
+{
+ int pipes[2];
+ int dummy;
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid > 0) {
+ // In parent process.
+ ::close(pipes[1]);
+
+ // Wait until all children have assigned the cgroup.
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
+ ::close(pipes[0]);
+
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+
+ // cgroups::destroy will reap all processes in the cgroup so we should
+ // *not* be able to reap it now.
+ int status;
+ EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
+ EXPECT_EQ(ECHILD, errno);
+ } else {
+ // In child process.
+
+ // We create 4 child processes here using two forks to test the case in
+ // which there are multiple active processes in the given cgroup.
+ ::fork();
+ ::fork();
+
+ // Put self into the test cgroup.
+ Try<Nothing> assign =
+ cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
+
+ if (assign.isError()) {
+ std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
+ abort();
+ }
+
+ // Notify the parent.
+ ::close(pipes[0]); // TODO(benh): Close after first fork?
+ if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
+ perror("Failed to notify the parent");
+ abort();
+ }
+ ::close(pipes[1]);
+
+ // Wait kill signal from parent.
+ while (true) {}
+
+ // Should not reach here.
+ std::cerr << "Reach an unreachable statement!" << std::endl;
+ abort();
+ }
+}
+
+
+void* threadFunction(void*)
+{
+ // Newly created threads have PTHREAD_CANCEL_ENABLE and
+ // PTHREAD_CANCEL_DEFERRED so they can be cancelled from the main thread.
+ while (true) { sleep(1); }
+
+ return NULL;
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_AssignThreads)
+{
+ size_t numThreads = 5;
+
+ pthread_t pthreads[numThreads];
+
+ // Create additional threads.
+ for (size_t i = 0; i < numThreads; i++)
+ {
+ EXPECT_EQ(0, pthread_create(&pthreads[i], NULL, threadFunction, NULL));
+ }
+
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ // Check the test cgroup is initially empty.
+ Try<set<pid_t> > cgroupThreads =
+ cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
+ EXPECT_SOME(cgroupThreads);
+ EXPECT_EQ(0u, cgroupThreads.get().size());
+
+ // Assign ourselves to the test cgroup.
+ CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
+
+ // Get our threads (may be more than the numThreads we created if
+ // other threads are running).
+ Try<set<pid_t> > threads = proc::threads(::getpid());
+ ASSERT_SOME(threads);
+
+ // Check the test cgroup now only contains all child threads.
+ cgroupThreads = cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
+ EXPECT_SOME(cgroupThreads);
+ EXPECT_SOME_EQ(threads.get(), cgroupThreads);
+
+ // Terminate the additional threads.
+ for (size_t i = 0; i < numThreads; i++)
+ {
+ EXPECT_EQ(0, pthread_cancel(pthreads[i]));
+ EXPECT_EQ(0, pthread_join(pthreads[i], NULL));
+ }
+
+ // Move ourselves to the root cgroup.
+ CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
+
+ // Destroy the cgroup.
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyStoppedProcess)
+{
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // In child process.
+ while (true) { sleep(1); }
+
+ ABORT("Child should not reach this statement");
+ }
+
+ // In parent process.
+
+ // Put child into the freezer cgroup.
+ Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
+
+ // Stop the child process.
+ EXPECT_EQ(0, kill(pid, SIGSTOP));
+
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+
+ // cgroups::destroy will reap all processes in the cgroup so we should
+ // *not* be able to reap it now.
+ int status;
+ EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
+ EXPECT_EQ(ECHILD, errno);
+}
+
+
+TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyTracedProcess)
+{
+ std::string hierarchy = path::join(baseHierarchy, "freezer");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // In child process.
+ while (true) { sleep(1); }
+
+ ABORT("Child should not reach this statement");
+ }
+
+ // In parent process.
+ Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
+ ASSERT_SOME(assign);
+
+ // Attach to the child process.
+ ASSERT_EQ(0, ptrace(PT_ATTACH, pid, NULL, NULL));
+
+ // Wait until the process is in traced state ('t' or 'T').
+ Duration elapsed = Duration::zero();
+ while (true) {
+ Result<proc::ProcessStatus> process = proc::status(pid);
+ ASSERT_SOME(process);
+
+ if (process.get().state == 'T' || process.get().state == 't') {
+ break;
+ }
+
+ if (elapsed > Seconds(1)) {
+ FAIL() << "Failed to wait for process to be traced";
+ }
+
+ os::sleep(Milliseconds(5));
+ elapsed += Milliseconds(5);
+ }
+
+ // Now destroy the cgroup.
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+
+ // cgroups::destroy will reap all processes in the cgroup so we should
+ // *not* be able to reap it now.
+ int status;
+ EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
+ EXPECT_EQ(ECHILD, errno);
+}
+
+
+class CgroupsAnyHierarchyWithPerfEventTest
+ : public CgroupsAnyHierarchyTest
+{
+public:
+ CgroupsAnyHierarchyWithPerfEventTest()
+ : CgroupsAnyHierarchyTest("perf_event") {}
+};
+
+
+TEST_F(CgroupsAnyHierarchyWithPerfEventTest, ROOT_CGROUPS_Perf)
+{
+ int pipes[2];
+ int dummy;
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ std::string hierarchy = path::join(baseHierarchy, "perf_event");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // In child process.
+ ::close(pipes[1]);
+
+ // Wait until parent has assigned us to the cgroup.
+ ssize_t len;
+ while ((len = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
+ errno == EINTR);
+ ASSERT_EQ((ssize_t) sizeof(dummy), len);
+ ::close(pipes[0]);
+
+ while (true) {
+ // Don't sleep so 'perf' can actually sample something.
+ }
+
+ ABORT("Child should not reach here");
+ }
+
+ // In parent.
+ ::close(pipes[0]);
+
+ // Put child into the test cgroup.
+ ASSERT_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid));
+
+ ssize_t len;
+ while ((len = ::write(pipes[1], &dummy, sizeof(dummy))) == -1 &&
+ errno == EINTR);
+ ASSERT_EQ((ssize_t) sizeof(dummy), len);
+ ::close(pipes[1]);
+
+ std::set<std::string> events;
+ // Hardware event.
+ events.insert("cycles");
+ // Software event.
+ events.insert("task-clock");
+
+ // NOTE: Wait at least 2 seconds as we've seen some variance in how
+ // well 'perf' does across Linux distributions (e.g., Ubuntu 14.04)
+ // and we want to make sure that we collect some non-zero values.
+ Future<mesos::PerfStatistics> statistics =
+ perf::sample(events, TEST_CGROUPS_ROOT, Seconds(2));
+ AWAIT_READY(statistics);
+
+ ASSERT_TRUE(statistics.get().has_cycles());
+
+ // TODO(benh): Some Linux distributions (Ubuntu 14.04) fail to
+ // properly sample 'cycles' with 'perf', so we don't explicitly
+ // check the value here. See MESOS-3082.
+ // EXPECT_LT(0u, statistics.get().cycles());
+
+ ASSERT_TRUE(statistics.get().has_task_clock());
+ EXPECT_LT(0.0, statistics.get().task_clock());
+
+ // Kill the child process.
+ ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ int status;
+ EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+
+ // Destroy the cgroup.
+ Future<Nothing> destroy = cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT);
+ AWAIT_READY(destroy);
+}
+
+
+class CgroupsAnyHierarchyMemoryPressureTest
+ : public CgroupsAnyHierarchyTest
+{
+public:
+ CgroupsAnyHierarchyMemoryPressureTest()
+ : CgroupsAnyHierarchyTest("memory"),
+ cgroup(TEST_CGROUPS_ROOT) {}
+
+protected:
+ virtual void SetUp()
+ {
+ CgroupsAnyHierarchyTest::SetUp();
+
+ hierarchy = path::join(baseHierarchy, "memory");
+
+ ASSERT_SOME(cgroups::create(hierarchy, cgroup));
+ }
+
+ void listen()
+ {
+ const std::vector<Level> levels = {
+ Level::LOW,
+ Level::MEDIUM,
+ Level::CRITICAL
+ };
+
+ foreach (Level level, levels) {
+ Try<Owned<Counter>> counter = Counter::create(hierarchy, cgroup, level);
+ EXPECT_SOME(counter);
+
+ counters[level] = counter.get();
+ }
+ }
+
+ std::string hierarchy;
+ const std::string cgroup;
+
+ hashmap<Level, Owned<Counter>> counters;
+};
+
+
+TEST_F(CgroupsAnyHierarchyMemoryPressureTest, ROOT_IncreaseUnlockedRSS)
+{
+ MemoryTestHelper helper;
+ ASSERT_SOME(helper.spawn());
+ ASSERT_SOME(helper.pid());
+
+ const Bytes limit = Megabytes(16);
+
+ // Move the memory test helper into a cgroup and set the limit.
+ EXPECT_SOME(cgroups::memory::limit_in_bytes(hierarchy, cgroup, limit));
+ EXPECT_SOME(cgroups::assign(hierarchy, cgroup, helper.pid().get()));
+
+ listen();
+
+ // Used to save the counter readings from last iteration.
+ uint64_t previousLow = 0;
+ uint64_t previousMedium = 0;
+ uint64_t previousCritical = 0;
+
+ // Used to save the counter readings from this iteration.
+ uint64_t low;
+ uint64_t medium;
+ uint64_t critical;
+
+ // Use a guard to error out if it's been too long.
+ // TODO(chzhcn): Use a better way to set testing time limit.
+ uint64_t iterationLimit = limit.bytes() / getpagesize() * 10;
+
+ for (uint64_t i = 0; i < iterationLimit; i++) {
+ EXPECT_SOME(helper.increaseRSS(getpagesize()));
+
+ Future<uint64_t> _low = counters[Level::LOW]->value();
+ Future<uint64_t> _medium = counters[Level::MEDIUM]->value();
+ Future<uint64_t> _critical = counters[Level::CRITICAL]->value();
+
+ AWAIT_READY(_low);
+ AWAIT_READY(_medium);
+ AWAIT_READY(_critical);
+
+ low = _low.get();
+ medium = _medium.get();
+ critical = _critical.get();
+
+ // We need to know the readings are the same as last time to be
+ // sure they are stable, because the reading is not atomic. For
+ // example, the medium could turn positive after we read low to be
+ // 0, but this should be fixed by the next read immediately.
+ if ((low == previousLow &&
+ medium == previousMedium &&
+ critical == previousCritical)) {
+ if (low != 0) {
+ EXPECT_LE(medium, low);
+ EXPECT_LE(critical, medium);
+
+ // When child's RSS is full, it will be OOM-kill'ed if we
+ // don't stop it right away.
+ break;
+ } else {
+ EXPECT_EQ(0u, medium);
+ EXPECT_EQ(0u, critical);
+ }
+ }
+
+ previousLow = low;
+ previousMedium = medium;
+ previousCritical = critical;
+ }
+}
+
+
+TEST_F(CgroupsAnyHierarchyMemoryPressureTest, ROOT_IncreasePageCache)
+{
+ MemoryTestHelper helper;
+ ASSERT_SOME(helper.spawn());
+ ASSERT_SOME(helper.pid());
+
+ const Bytes limit = Megabytes(16);
+
+ // Move the memory test helper into a cgroup and set the limit.
+ EXPECT_SOME(cgroups::memory::limit_in_bytes(hierarchy, cgroup, limit));
+ EXPECT_SOME(cgroups::assign(hierarchy, cgroup, helper.pid().get()));
+
+ listen();
+
+ // Used to save the counter readings from last iteration.
+ uint64_t previousLow = 0;
+ uint64_t previousMedium = 0;
+ uint64_t previousCritical = 0;
+
+ // Used to save the counter readings from this iteration.
+ uint64_t low;
+ uint64_t medium;
+ uint64_t critical;
+
+ // Use a guard to error out if it's been too long.
+ // TODO(chzhcn): Use a better way to set testing time limit.
+ uint64_t iterationLimit = limit.bytes() / Megabytes(1).bytes() * 2;
+
+ for (uint64_t i = 0; i < iterationLimit; i++) {
+ EXPECT_SOME(helper.increasePageCache(Megabytes(1)));
+
+ Future<uint64_t> _low = counters[Level::LOW]->value();
+ Future<uint64_t> _medium = counters[Level::MEDIUM]->value();
+ Future<uint64_t> _critical = counters[Level::CRITICAL]->value();
+
+ AWAIT_READY(_low);
+ AWAIT_READY(_medium);
+ AWAIT_READY(_critical);
+
+ low = _low.get();
+ medium = _medium.get();
+ critical = _critical.get();
+
+ // We need to know the readings are the same as last time to be
+ // sure they are stable, because the reading is not atomic. For
+ // example, the medium could turn positive after we read low to be
+ // 0, but this should be fixed by the next read immediately.
+ if ((low == previousLow &&
+ medium == previousMedium &&
+ critical == previousCritical)) {
+ if (low != 0) {
+ EXPECT_LE(medium, low);
+ EXPECT_LE(critical, medium);
+
+ // Different from the RSS test, since the child is only
+ // consuming at a slow rate the page cache, which is evictable
+ // and reclaimable, we could therefore be in this state
+ // forever. Our guard will let us out shortly.
+ } else {
+ EXPECT_EQ(0u, medium);
+ EXPECT_EQ(0u, critical);
+ }
+ }
+
+ previousLow = low;
+ previousMedium = medium;
+ previousCritical = critical;
+ }
+
+ EXPECT_LT(0u, low);
+}
+
+// Tests the cpuacct::stat API. This test just tests for ANY value returned by
+// the API.
+TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_CpuAcctsStats)
+{
+ const std::string hierarchy = path::join(baseHierarchy, "cpuacct");
+ ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
+
+ CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
+
+ ASSERT_SOME(cgroups::cpuacct::stat(hierarchy, TEST_CGROUPS_ROOT));
+
+ // Move ourselves to the root cgroup.
+ CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
+
+ AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/composing_containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/composing_containerizer_tests.cpp b/src/tests/containerizer/composing_containerizer_tests.cpp
new file mode 100644
index 0000000..d66f519
--- /dev/null
+++ b/src/tests/containerizer/composing_containerizer_tests.cpp
@@ -0,0 +1,171 @@
+/**
+ * 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 <gtest/gtest.h>
+
+#include <vector>
+
+#include <process/future.hpp>
+#include <process/gmock.hpp>
+
+#include <stout/option.hpp>
+
+#include "messages/messages.hpp"
+
+#include "slave/containerizer/containerizer.hpp"
+#include "slave/containerizer/composing.hpp"
+
+#include "tests/mesos.hpp"
+
+using namespace mesos::internal::slave;
+
+using namespace process;
+
+using std::vector;
+
+using testing::_;
+using testing::Return;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+class ComposingContainerizerTest : public MesosTest {};
+
+class MockContainerizer : public slave::Containerizer
+{
+public:
+ MOCK_METHOD1(
+ recover,
+ process::Future<Nothing>(
+ const Option<slave::state::SlaveState>&));
+
+ MOCK_METHOD7(
+ launch,
+ process::Future<bool>(
+ const ContainerID&,
+ const ExecutorInfo&,
+ const std::string&,
+ const Option<std::string>&,
+ const SlaveID&,
+ const process::PID<Slave>&,
+ bool));
+
+ MOCK_METHOD8(
+ launch,
+ process::Future<bool>(
+ const ContainerID&,
+ const TaskInfo&,
+ const ExecutorInfo&,
+ const std::string&,
+ const Option<std::string>&,
+ const SlaveID&,
+ const process::PID<Slave>&,
+ bool));
+
+ MOCK_METHOD2(
+ update,
+ process::Future<Nothing>(
+ const ContainerID&,
+ const Resources&));
+
+ MOCK_METHOD1(
+ usage,
+ process::Future<ResourceStatistics>(
+ const ContainerID&));
+
+ MOCK_METHOD1(
+ wait,
+ process::Future<containerizer::Termination>(
+ const ContainerID&));
+
+ MOCK_METHOD1(
+ destroy,
+ void(const ContainerID&));
+
+ MOCK_METHOD0(
+ containers,
+ process::Future<hashset<ContainerID> >());
+};
+
+
+// This test checks if destroy is called while container is being
+// launched, the composing containerizer still calls the underlying
+// containerizer's destroy and skip calling the rest of the
+// containerizers.
+TEST_F(ComposingContainerizerTest, DestroyWhileLaunching)
+{
+ vector<Containerizer*> containerizers;
+
+ MockContainerizer* mockContainerizer = new MockContainerizer();
+ MockContainerizer* mockContainerizer2 = new MockContainerizer();
+
+ containerizers.push_back(mockContainerizer);
+ containerizers.push_back(mockContainerizer2);
+
+ ComposingContainerizer containerizer(containerizers);
+ ContainerID containerId;
+ containerId.set_value("container");
+ TaskInfo taskInfo;
+ ExecutorInfo executorInfo;
+ SlaveID slaveId;
+ PID<Slave> slavePid;
+
+ Promise<bool> launchPromise;
+
+ EXPECT_CALL(*mockContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(Return(launchPromise.future()));
+
+ Future<Nothing> destroy;
+
+ EXPECT_CALL(*mockContainerizer, destroy(_))
+ .WillOnce(FutureSatisfy(&destroy));
+
+ Future<bool> launch = containerizer.launch(
+ containerId,
+ taskInfo,
+ executorInfo,
+ "dir",
+ "user",
+ slaveId,
+ slavePid,
+ false);
+
+ Resources resources = Resources::parse("cpus:1;mem:256").get();
+
+ EXPECT_TRUE(launch.isPending());
+
+ containerizer.destroy(containerId);
+
+ EXPECT_CALL(*mockContainerizer2, launch(_, _, _, _, _, _, _, _))
+ .Times(0);
+
+ // We make sure the destroy is being called on the first containerizer.
+ // The second containerizer shouldn't be called as well since the
+ // container is already destroyed.
+ AWAIT_READY(destroy);
+
+ launchPromise.set(false);
+ AWAIT_FAILED(launch);
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/containerizer_tests.cpp b/src/tests/containerizer/containerizer_tests.cpp
new file mode 100644
index 0000000..a44b6e8
--- /dev/null
+++ b/src/tests/containerizer/containerizer_tests.cpp
@@ -0,0 +1,732 @@
+/**
+ * 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 <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include <mesos/mesos.hpp>
+
+#include <mesos/slave/isolator.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+
+#include <stout/strings.hpp>
+
+#include "slave/flags.hpp"
+
+#include "slave/containerizer/fetcher.hpp"
+#include "slave/containerizer/launcher.hpp"
+
+#include "slave/containerizer/mesos/containerizer.hpp"
+
+#include "tests/flags.hpp"
+#include "tests/mesos.hpp"
+#include "tests/utils.hpp"
+
+#include "tests/containerizer/isolator.hpp"
+#include "tests/containerizer/launcher.hpp"
+
+using namespace process;
+
+using mesos::internal::master::Master;
+
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::Launcher;
+using mesos::internal::slave::MesosContainerizer;
+using mesos::internal::slave::MesosContainerizerProcess;
+using mesos::internal::slave::PosixLauncher;
+using mesos::internal::slave::Provisioner;
+using mesos::internal::slave::Slave;
+
+using mesos::internal::slave::state::ExecutorState;
+using mesos::internal::slave::state::FrameworkState;
+using mesos::internal::slave::state::RunState;
+using mesos::internal::slave::state::SlaveState;
+
+using mesos::slave::ExecutorLimitation;
+using mesos::slave::ExecutorRunState;
+using mesos::slave::Isolator;
+using mesos::slave::IsolatorProcess;
+
+using std::list;
+using std::map;
+using std::string;
+using std::vector;
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class MesosContainerizerIsolatorPreparationTest :
+ public TemporaryDirectoryTest
+{
+public:
+ // Construct a MesosContainerizer with TestIsolator(s) which use the provided
+ // 'prepare' command(s).
+ Try<MesosContainerizer*> CreateContainerizer(
+ Fetcher* fetcher,
+ const vector<Option<CommandInfo>>& prepares)
+ {
+ vector<Owned<Isolator>> isolators;
+
+ foreach (const Option<CommandInfo>& prepare, prepares) {
+ Try<Isolator*> isolator = TestIsolatorProcess::create(prepare);
+ if (isolator.isError()) {
+ return Error(isolator.error());
+ }
+
+ isolators.push_back(Owned<Isolator>(isolator.get()));
+ }
+
+ slave::Flags flags;
+ flags.launcher_dir = path::join(tests::flags.build_dir, "src");
+
+ Try<Launcher*> launcher = PosixLauncher::create(flags);
+ if (launcher.isError()) {
+ return Error(launcher.error());
+ }
+
+ return new MesosContainerizer(
+ flags,
+ false,
+ fetcher,
+ Owned<Launcher>(launcher.get()),
+ isolators,
+ hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
+ }
+
+ Try<MesosContainerizer*> CreateContainerizer(
+ Fetcher* fetcher,
+ const Option<CommandInfo>& prepare)
+ {
+ vector<Option<CommandInfo>> prepares;
+ prepares.push_back(prepare);
+
+ return CreateContainerizer(fetcher, prepares);
+ }
+};
+
+
+// The isolator has a prepare command that succeeds.
+TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptSucceeds)
+{
+ string directory = os::getcwd(); // We're inside a temporary sandbox.
+ string file = path::join(directory, "child.script.executed");
+
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer = CreateContainerizer(
+ &fetcher,
+ CREATE_COMMAND_INFO("touch " + file));
+
+ CHECK_SOME(containerizer);
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ Future<bool> launch = containerizer.get()->launch(
+ containerId,
+ CREATE_EXECUTOR_INFO("executor", "exit 0"),
+ directory,
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ // Wait until the launch completes.
+ AWAIT_READY(launch);
+
+ // Wait for the child (preparation script + executor) to complete.
+ Future<containerizer::Termination> wait =
+ containerizer.get()->wait(containerId);
+
+ AWAIT_READY(wait);
+
+ // Check the child exited correctly.
+ EXPECT_TRUE(wait.get().has_status());
+ EXPECT_EQ(0, wait.get().status());
+
+ // Check the preparation script actually ran.
+ EXPECT_TRUE(os::exists(file));
+
+ // Destroy the container.
+ containerizer.get()->destroy(containerId);
+
+ delete containerizer.get();
+}
+
+
+// The isolator has a prepare command that fails.
+TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptFails)
+{
+ string directory = os::getcwd(); // We're inside a temporary sandbox.
+ string file = path::join(directory, "child.script.executed");
+
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer = CreateContainerizer(
+ &fetcher,
+ CREATE_COMMAND_INFO("touch " + file + " && exit 1"));
+
+ CHECK_SOME(containerizer);
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ Future<bool> launch = containerizer.get()->launch(
+ containerId,
+ CREATE_EXECUTOR_INFO("executor", "exit 0"),
+ directory,
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ // Wait until the launch completes.
+ AWAIT_READY(launch);
+
+ // Wait for the child (preparation script + executor) to complete.
+ Future<containerizer::Termination> wait =
+ containerizer.get()->wait(containerId);
+
+ AWAIT_READY(wait);
+
+ // Check the child failed to exit correctly.
+ EXPECT_TRUE(wait.get().has_status());
+ EXPECT_NE(0, wait.get().status());
+
+ // Check the preparation script actually ran.
+ EXPECT_TRUE(os::exists(file));
+
+ // Destroy the container.
+ containerizer.get()->destroy(containerId);
+
+ delete containerizer.get();
+}
+
+
+// There are two isolators, one with a prepare command that succeeds
+// and another that fails. The execution order is not defined but the
+// launch should fail from the failing prepare command.
+TEST_F(MesosContainerizerIsolatorPreparationTest, MultipleScripts)
+{
+ string directory = os::getcwd(); // We're inside a temporary sandbox.
+ string file1 = path::join(directory, "child.script.executed.1");
+ string file2 = path::join(directory, "child.script.executed.2");
+
+ vector<Option<CommandInfo>> prepares;
+
+ // This isolator prepare command one will succeed if called first,
+ // otherwise it won't get run.
+ prepares.push_back(CREATE_COMMAND_INFO("touch " + file1 + " && exit 0"));
+
+ // This will fail, either first or after the successful command.
+ prepares.push_back(CREATE_COMMAND_INFO("touch " + file2 + " && exit 1"));
+
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer =
+ CreateContainerizer(&fetcher, prepares);
+
+ CHECK_SOME(containerizer);
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ Future<bool> launch = containerizer.get()->launch(
+ containerId,
+ CREATE_EXECUTOR_INFO("executor", "exit 0"),
+ directory,
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ // Wait until the launch completes.
+ AWAIT_READY(launch);
+
+ // Wait for the child (preparation script(s) + executor) to complete.
+ Future<containerizer::Termination> wait =
+ containerizer.get()->wait(containerId);
+ AWAIT_READY(wait);
+
+ // Check the child failed to exit correctly.
+ EXPECT_TRUE(wait.get().has_status());
+ EXPECT_NE(0, wait.get().status());
+
+ // Check the failing preparation script has actually ran.
+ EXPECT_TRUE(os::exists(file2));
+
+ // Destroy the container.
+ containerizer.get()->destroy(containerId);
+
+ delete containerizer.get();
+}
+
+
+class MesosContainerizerExecuteTest : public TemporaryDirectoryTest {};
+
+
+TEST_F(MesosContainerizerExecuteTest, IoRedirection)
+{
+ string directory = os::getcwd(); // We're inside a temporary sandbox.
+
+ slave::Flags flags;
+ flags.launcher_dir = path::join(tests::flags.build_dir, "src");
+
+ Fetcher fetcher;
+
+ // Use local=false so std{err,out} are redirected to files.
+ Try<MesosContainerizer*> containerizer =
+ MesosContainerizer::create(flags, false, &fetcher);
+
+ ASSERT_SOME(containerizer);
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ string errMsg = "this is stderr";
+ string outMsg = "this is stdout";
+ string command =
+ "(echo '" + errMsg + "' 1>&2) && echo '" + outMsg + "'";
+
+ Future<bool> launch = containerizer.get()->launch(
+ containerId,
+ CREATE_EXECUTOR_INFO("executor", command),
+ directory,
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ // Wait for the launch to complete.
+ AWAIT_READY(launch);
+
+ // Wait on the container.
+ Future<containerizer::Termination> wait =
+ containerizer.get()->wait(containerId);
+
+ AWAIT_READY(wait);
+
+ // Check the executor exited correctly.
+ EXPECT_TRUE(wait.get().has_status());
+ EXPECT_EQ(0, wait.get().status());
+
+ // Check that std{err, out} was redirected.
+ // NOTE: Fetcher uses GLOG, which outputs extra information to
+ // stderr.
+ Try<string> stderr = os::read(path::join(directory, "stderr"));
+ ASSERT_SOME(stderr);
+ EXPECT_TRUE(strings::contains(stderr.get(), errMsg));
+
+ EXPECT_SOME_EQ(outMsg + "\n", os::read(path::join(directory, "stdout")));
+
+ delete containerizer.get();
+}
+
+
+class MesosContainerizerDestroyTest : public MesosTest {};
+
+
+class MockMesosContainerizerProcess : public MesosContainerizerProcess
+{
+public:
+ MockMesosContainerizerProcess(
+ const slave::Flags& flags,
+ bool local,
+ Fetcher* fetcher,
+ const Owned<Launcher>& launcher,
+ const vector<Owned<Isolator>>& isolators,
+ const hashmap<ContainerInfo::Image::Type,
+ Owned<Provisioner>>& provisioners)
+ : MesosContainerizerProcess(
+ flags,
+ local,
+ fetcher,
+ launcher,
+ isolators,
+ provisioners)
+ {
+ // NOTE: See TestContainerizer::setup for why we use
+ // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
+ // 'ON_CALL' and 'WillByDefault'.
+ EXPECT_CALL(*this, exec(_, _))
+ .WillRepeatedly(Invoke(this, &MockMesosContainerizerProcess::_exec));
+ }
+
+ MOCK_METHOD2(
+ exec,
+ Future<bool>(
+ const ContainerID& containerId,
+ int pipeWrite));
+
+ Future<bool> _exec(
+ const ContainerID& containerId,
+ int pipeWrite)
+ {
+ return MesosContainerizerProcess::exec(
+ containerId,
+ pipeWrite);
+ }
+};
+
+
+class MockIsolator : public mesos::slave::Isolator
+{
+public:
+ MockIsolator()
+ {
+ EXPECT_CALL(*this, watch(_))
+ .WillRepeatedly(Return(watchPromise.future()));
+
+ EXPECT_CALL(*this, isolate(_, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ EXPECT_CALL(*this, cleanup(_))
+ .WillRepeatedly(Return(Nothing()));
+
+ EXPECT_CALL(*this, prepare(_, _, _, _, _))
+ .WillRepeatedly(Invoke(this, &MockIsolator::_prepare));
+ }
+
+ MOCK_METHOD2(
+ recover,
+ Future<Nothing>(
+ const list<ExecutorRunState>&,
+ const hashset<ContainerID>&));
+
+ MOCK_METHOD5(
+ prepare,
+ Future<Option<CommandInfo>>(
+ const ContainerID&,
+ const ExecutorInfo&,
+ const string&,
+ const Option<string>&,
+ const Option<string>&));
+
+ virtual Future<Option<CommandInfo>> _prepare(
+ const ContainerID& containerId,
+ const ExecutorInfo& executorInfo,
+ const string& directory,
+ const Option<string>& rootfs,
+ const Option<string>& user)
+ {
+ return None();
+ }
+
+ MOCK_METHOD2(
+ isolate,
+ Future<Nothing>(const ContainerID&, pid_t));
+
+ MOCK_METHOD1(
+ watch,
+ Future<mesos::slave::ExecutorLimitation>(const ContainerID&));
+
+ MOCK_METHOD2(
+ update,
+ Future<Nothing>(const ContainerID&, const Resources&));
+
+ MOCK_METHOD1(
+ usage,
+ Future<ResourceStatistics>(const ContainerID&));
+
+ MOCK_METHOD1(
+ cleanup,
+ Future<Nothing>(const ContainerID&));
+
+ Promise<mesos::slave::ExecutorLimitation> watchPromise;
+};
+
+
+// Destroying a mesos containerizer while it is fetching should
+// complete without waiting for the fetching to finish.
+TEST_F(MesosContainerizerDestroyTest, DestroyWhileFetching)
+{
+ slave::Flags flags = CreateSlaveFlags();
+
+ Try<Launcher*> launcher = PosixLauncher::create(flags);
+ ASSERT_SOME(launcher);
+
+ Fetcher fetcher;
+
+ MockMesosContainerizerProcess* process = new MockMesosContainerizerProcess(
+ flags,
+ true,
+ &fetcher,
+ Owned<Launcher>(launcher.get()),
+ vector<Owned<Isolator>>(),
+ hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
+
+ Future<Nothing> exec;
+ Promise<bool> promise;
+
+ // Letting exec hang to simulate a long fetch.
+ EXPECT_CALL(*process, exec(_, _))
+ .WillOnce(DoAll(FutureSatisfy(&exec),
+ Return(promise.future())));
+
+ MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ TaskInfo taskInfo;
+ CommandInfo commandInfo;
+ taskInfo.mutable_command()->MergeFrom(commandInfo);
+
+ containerizer.launch(
+ containerId,
+ taskInfo,
+ CREATE_EXECUTOR_INFO("executor", "exit 0"),
+ os::getcwd(),
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ Future<containerizer::Termination> wait = containerizer.wait(containerId);
+
+ AWAIT_READY(exec);
+
+ containerizer.destroy(containerId);
+
+ // The container should still exit even if fetch didn't complete.
+ AWAIT_READY(wait);
+}
+
+
+// Destroying a mesos containerizer while it is preparing should wait
+// until isolators are finished preparing before destroying.
+TEST_F(MesosContainerizerDestroyTest, DestroyWhilePreparing)
+{
+ slave::Flags flags = CreateSlaveFlags();
+
+ Try<Launcher*> launcher = PosixLauncher::create(flags);
+ ASSERT_SOME(launcher);
+
+ MockIsolator* isolator = new MockIsolator();
+
+ Future<Nothing> prepare;
+ Promise<Option<CommandInfo>> promise;
+
+ // Simulate a long prepare from the isolator.
+ EXPECT_CALL(*isolator, prepare(_, _, _, _, _))
+ .WillOnce(DoAll(FutureSatisfy(&prepare),
+ Return(promise.future())));
+
+ Fetcher fetcher;
+
+ MockMesosContainerizerProcess* process = new MockMesosContainerizerProcess(
+ flags,
+ true,
+ &fetcher,
+ Owned<Launcher>(launcher.get()),
+ {Owned<Isolator>(isolator)},
+ hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
+
+ MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ TaskInfo taskInfo;
+ CommandInfo commandInfo;
+ taskInfo.mutable_command()->MergeFrom(commandInfo);
+
+ containerizer.launch(
+ containerId,
+ taskInfo,
+ CREATE_EXECUTOR_INFO("executor", "exit 0"),
+ os::getcwd(),
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ Future<containerizer::Termination> wait = containerizer.wait(containerId);
+
+ AWAIT_READY(prepare);
+
+ containerizer.destroy(containerId);
+
+ // The container should not exit until prepare is complete.
+ ASSERT_TRUE(wait.isPending());
+
+ // Need to help the compiler to disambiguate between overloads.
+ Option<CommandInfo> option = commandInfo;
+ promise.set(option);
+
+ AWAIT_READY(wait);
+
+ containerizer::Termination termination = wait.get();
+
+ EXPECT_EQ(
+ "Container destroyed while preparing isolators",
+ termination.message());
+
+ EXPECT_TRUE(termination.killed());
+ EXPECT_FALSE(termination.has_status());
+}
+
+
+// This action destroys the container using the real launcher and
+// waits until the destroy is complete.
+ACTION_P(InvokeDestroyAndWait, launcher)
+{
+ Future<Nothing> destroy = launcher->real->destroy(arg0);
+ AWAIT_READY(destroy);
+}
+
+
+// This test verifies that when a container destruction fails the
+// 'container_destroy_errors' metric is updated.
+TEST_F(MesosContainerizerDestroyTest, LauncherDestroyFailure)
+{
+ // Create a TestLauncher backed by PosixLauncher.
+ slave::Flags flags = CreateSlaveFlags();
+
+ Try<Launcher*> launcher_ = PosixLauncher::create(flags);
+ ASSERT_SOME(launcher_);
+
+ TestLauncher* launcher = new TestLauncher(Owned<Launcher>(launcher_.get()));
+
+ Fetcher fetcher;
+
+ MesosContainerizerProcess* process = new MesosContainerizerProcess(
+ flags,
+ true,
+ &fetcher,
+ Owned<Launcher>(launcher),
+ vector<Owned<Isolator>>(),
+ hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
+
+ MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
+
+ ContainerID containerId;
+ containerId.set_value("test_container");
+
+ TaskInfo taskInfo;
+ CommandInfo commandInfo;
+ taskInfo.mutable_command()->MergeFrom(commandInfo);
+
+ // Destroy the container using the PosixLauncher but return a failed
+ // future to the containerizer.
+ EXPECT_CALL(*launcher, destroy(_))
+ .WillOnce(DoAll(InvokeDestroyAndWait(launcher),
+ Return(Failure("Destroy failure"))));
+
+ Future<bool> launch = containerizer.launch(
+ containerId,
+ taskInfo,
+ CREATE_EXECUTOR_INFO("executor", "sleep 1000"),
+ os::getcwd(),
+ None(),
+ SlaveID(),
+ PID<Slave>(),
+ false);
+
+ AWAIT_READY(launch);
+
+ Future<containerizer::Termination> wait = containerizer.wait(containerId);
+
+ containerizer.destroy(containerId);
+
+ // The container destroy should fail.
+ AWAIT_FAILED(wait);
+
+ // 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();
+
+ // Ensure that the metric is updated.
+ JSON::Object metrics = Metrics();
+ ASSERT_EQ(
+ 1u,
+ metrics.values.count("containerizer/mesos/container_destroy_errors"));
+ ASSERT_EQ(
+ 1u,
+ metrics.values["containerizer/mesos/container_destroy_errors"]);
+}
+
+
+class MesosContainerizerRecoverTest : public MesosTest {};
+
+
+// This test checks that MesosContainerizer doesn't recover executors
+// that were started by another containerizer (e.g: Docker).
+TEST_F(MesosContainerizerRecoverTest, SkipRecoverNonMesosContainers)
+{
+ slave::Flags flags = CreateSlaveFlags();
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer =
+ MesosContainerizer::create(flags, true, &fetcher);
+
+ ASSERT_SOME(containerizer);
+
+ ExecutorID executorId;
+ executorId.set_value(UUID::random().toString());
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_container()->set_type(ContainerInfo::DOCKER);
+
+ ExecutorState executorState;
+ executorState.info = executorInfo;
+ executorState.latest = containerId;
+
+ RunState runState;
+ runState.id = containerId;
+ executorState.runs.put(containerId, runState);
+
+ FrameworkState frameworkState;
+ frameworkState.executors.put(executorId, executorState);
+
+ SlaveState slaveState;
+ FrameworkID frameworkId;
+ frameworkId.set_value(UUID::random().toString());
+ slaveState.frameworks.put(frameworkId, frameworkState);
+
+ Future<Nothing> recover = containerizer.get()->recover(slaveState);
+ AWAIT_READY(recover);
+
+ Future<hashset<ContainerID>> containers = containerizer.get()->containers();
+ AWAIT_READY(containers);
+ EXPECT_EQ(0u, containers.get().size());
+
+ delete containerizer.get();
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
[09/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/docker_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/docker_tests.cpp b/src/tests/containerizer/docker_tests.cpp
new file mode 100644
index 0000000..a4a2725
--- /dev/null
+++ b/src/tests/containerizer/docker_tests.cpp
@@ -0,0 +1,421 @@
+/**
+ * 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 <gtest/gtest.h>
+
+#include <process/future.hpp>
+#include <process/gtest.hpp>
+#include <process/owned.hpp>
+#include <process/subprocess.hpp>
+
+#include <stout/duration.hpp>
+#include <stout/option.hpp>
+#include <stout/gtest.hpp>
+
+#include "docker/docker.hpp"
+
+#include "mesos/resources.hpp"
+
+#include "tests/environment.hpp"
+#include "tests/flags.hpp"
+#include "tests/mesos.hpp"
+
+using namespace process;
+
+using std::list;
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+static const string NAME_PREFIX="mesos-docker";
+
+
+class DockerTest : public MesosTest
+{
+ virtual void TearDown()
+ {
+ Try<Docker*> docker = Docker::create(tests::flags.docker, false);
+ ASSERT_SOME(docker);
+
+ Future<list<Docker::Container>> containers =
+ docker.get()->ps(true, NAME_PREFIX);
+
+ AWAIT_READY(containers);
+
+ // Cleanup all mesos launched containers.
+ foreach (const Docker::Container& container, containers.get()) {
+ AWAIT_READY_FOR(docker.get()->rm(container.id, true), Seconds(30));
+ }
+
+ delete docker.get();
+ }
+};
+
+// This test tests the functionality of the docker's interfaces.
+TEST_F(DockerTest, ROOT_DOCKER_interface)
+{
+ const string containerName = NAME_PREFIX + "-test";
+ Resources resources = Resources::parse("cpus:1;mem:512").get();
+
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ // Verify that we do not see the container.
+ Future<list<Docker::Container> > containers = docker->ps(true, containerName);
+ AWAIT_READY(containers);
+ foreach (const Docker::Container& container, containers.get()) {
+ EXPECT_NE("/" + containerName, container.name);
+ }
+
+ Try<string> directory = environment->mkdtemp();
+ CHECK_SOME(directory) << "Failed to create temporary directory";
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_value("sleep 120");
+
+ // Start the container.
+ Future<Nothing> status = docker->run(
+ containerInfo,
+ commandInfo,
+ containerName,
+ directory.get(),
+ "/mnt/mesos/sandbox",
+ resources);
+
+ Future<Docker::Container> inspect =
+ docker->inspect(containerName, Seconds(1));
+ AWAIT_READY(inspect);
+
+ // Should be able to see the container now.
+ containers = docker->ps();
+ AWAIT_READY(containers);
+ bool found = false;
+ foreach (const Docker::Container& container, containers.get()) {
+ if ("/" + containerName == container.name) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ // Test some fields of the container.
+ EXPECT_NE("", inspect.get().id);
+ EXPECT_EQ("/" + containerName, inspect.get().name);
+ EXPECT_SOME(inspect.get().pid);
+
+ // Stop the container.
+ status = docker->stop(containerName);
+ AWAIT_READY(status);
+
+ // Now, the container should not appear in the result of ps().
+ // But it should appear in the result of ps(true).
+ containers = docker->ps();
+ AWAIT_READY(containers);
+ foreach (const Docker::Container& container, containers.get()) {
+ EXPECT_NE("/" + containerName, container.name);
+ }
+
+ containers = docker->ps(true, containerName);
+ AWAIT_READY(containers);
+ found = false;
+ foreach (const Docker::Container& container, containers.get()) {
+ if ("/" + containerName == container.name) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ // Check the container's info, both id and name should remain the
+ // same since we haven't removed it, but the pid should be none
+ // since it's not running.
+ inspect = docker->inspect(containerName);
+ AWAIT_READY(inspect);
+
+ EXPECT_NE("", inspect.get().id);
+ EXPECT_EQ("/" + containerName, inspect.get().name);
+ EXPECT_NONE(inspect.get().pid);
+
+ // Remove the container.
+ status = docker->rm(containerName);
+ AWAIT_READY(status);
+
+ // Should not be able to inspect the container.
+ inspect = docker->inspect(containerName);
+ AWAIT_FAILED(inspect);
+
+ // Also, now we should not be able to see the container by invoking
+ // ps(true).
+ containers = docker->ps(true, containerName);
+ AWAIT_READY(containers);
+ foreach (const Docker::Container& container, containers.get()) {
+ EXPECT_NE("/" + containerName, container.name);
+ }
+
+ // Start the container again, this time we will do a "rm -f"
+ // directly, instead of stopping and rm.
+ status = docker->run(
+ containerInfo,
+ commandInfo,
+ containerName,
+ directory.get(),
+ "/mnt/mesos/sandbox",
+ resources);
+
+ inspect = docker->inspect(containerName, Seconds(1));
+ AWAIT_READY(inspect);
+
+ // Verify that the container is there.
+ containers = docker->ps();
+ AWAIT_READY(containers);
+ found = false;
+ foreach (const Docker::Container& container, containers.get()) {
+ if ("/" + containerName == container.name) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ // Then do a "rm -f".
+ status = docker->rm(containerName, true);
+ AWAIT_READY(status);
+
+ // Verify that the container is totally removed, that is we can't
+ // find it by ps() or ps(true).
+ containers = docker->ps();
+ AWAIT_READY(containers);
+ foreach (const Docker::Container& container, containers.get()) {
+ EXPECT_NE("/" + containerName, container.name);
+ }
+ containers = docker->ps(true, containerName);
+ AWAIT_READY(containers);
+ foreach (const Docker::Container& container, containers.get()) {
+ EXPECT_NE("/" + containerName, container.name);
+ }
+}
+
+
+TEST_F(DockerTest, ROOT_DOCKER_CheckCommandWithShell)
+{
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_shell(true);
+
+ Future<Nothing> run = docker->run(
+ containerInfo,
+ commandInfo,
+ "testContainer",
+ "dir",
+ "/mnt/mesos/sandbox");
+
+ ASSERT_TRUE(run.isFailed());
+}
+
+
+TEST_F(DockerTest, ROOT_DOCKER_CheckPortResource)
+{
+ const string containerName = NAME_PREFIX + "-port-resource-test";
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ // Make sure the container is removed.
+ Future<Nothing> remove = docker->rm(containerName, true);
+
+ ASSERT_TRUE(process::internal::await(remove, Seconds(10)));
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
+
+ ContainerInfo::DockerInfo::PortMapping portMapping;
+ portMapping.set_host_port(10000);
+ portMapping.set_container_port(80);
+
+ dockerInfo.add_port_mappings()->CopyFrom(portMapping);
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_shell(false);
+ commandInfo.set_value("true");
+
+ Resources resources =
+ Resources::parse("ports:[9998-9999];ports:[10001-11000]").get();
+
+ Future<Nothing> run = docker->run(
+ containerInfo,
+ commandInfo,
+ containerName,
+ "dir",
+ "/mnt/mesos/sandbox",
+ resources);
+
+ // Port should be out side of the provided ranges.
+ AWAIT_EXPECT_FAILED(run);
+
+ resources = Resources::parse("ports:[9998-9999];ports:[10000-11000]").get();
+
+ Try<string> directory = environment->mkdtemp();
+ CHECK_SOME(directory) << "Failed to create temporary directory";
+
+ run = docker->run(
+ containerInfo,
+ commandInfo,
+ containerName,
+ directory.get(),
+ "/mnt/mesos/sandbox",
+ resources);
+
+ AWAIT_READY(run);
+}
+
+
+TEST_F(DockerTest, ROOT_DOCKER_CancelPull)
+{
+ // Delete the test image if it exists.
+
+ Try<Subprocess> s = process::subprocess(
+ tests::flags.docker + " rmi lingmann/1gb",
+ Subprocess::PATH("/dev/null"),
+ Subprocess::PATH("/dev/null"),
+ Subprocess::PATH("/dev/null"));
+
+ ASSERT_SOME(s);
+
+ AWAIT_READY_FOR(s.get().status(), Seconds(30));
+
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ Try<string> directory = environment->mkdtemp();
+
+ CHECK_SOME(directory) << "Failed to create temporary directory";
+
+ // Assume that pulling the very large image 'lingmann/1gb' will take
+ // sufficiently long that we can start it and discard (i.e., cancel
+ // it) right away and the future will indeed get discarded.
+ Future<Docker::Image> future =
+ docker->pull(directory.get(), "lingmann/1gb");
+
+ future.discard();
+
+ AWAIT_DISCARDED(future);
+}
+
+
+// This test verifies mounting in a relative path when running a
+// docker container works.
+TEST_F(DockerTest, ROOT_DOCKER_MountRelative)
+{
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ Volume* volume = containerInfo.add_volumes();
+ volume->set_host_path("test_file");
+ volume->set_container_path("/tmp/test_file");
+ volume->set_mode(Volume::RO);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_shell(true);
+ commandInfo.set_value("ls /tmp/test_file");
+
+ Try<string> directory = environment->mkdtemp();
+ CHECK_SOME(directory) << "Failed to create temporary directory";
+
+ const string testFile = path::join(directory.get(), "test_file");
+ EXPECT_SOME(os::write(testFile, "data"));
+
+ Future<Nothing> run = docker->run(
+ containerInfo,
+ commandInfo,
+ NAME_PREFIX + "-mount-relative-test",
+ directory.get(),
+ directory.get());
+
+ AWAIT_READY(run);
+}
+
+
+// This test verifies mounting in a absolute path when running a
+// docker container works.
+TEST_F(DockerTest, ROOT_DOCKER_MountAbsolute)
+{
+ Owned<Docker> docker(Docker::create(tests::flags.docker, false).get());
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ Try<string> directory = environment->mkdtemp();
+ CHECK_SOME(directory) << "Failed to create temporary directory";
+
+ const string testFile = path::join(directory.get(), "test_file");
+ EXPECT_SOME(os::write(testFile, "data"));
+
+ Volume* volume = containerInfo.add_volumes();
+ volume->set_host_path(testFile);
+ volume->set_container_path("/tmp/test_file");
+ volume->set_mode(Volume::RO);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_shell(true);
+ commandInfo.set_value("ls /tmp/test_file");
+
+ Future<Nothing> run = docker->run(
+ containerInfo,
+ commandInfo,
+ NAME_PREFIX + "-mount-absolute-test",
+ directory.get(),
+ directory.get());
+
+ AWAIT_READY(run);
+}
+
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/external_containerizer_test.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/external_containerizer_test.cpp b/src/tests/containerizer/external_containerizer_test.cpp
new file mode 100644
index 0000000..4f152a4
--- /dev/null
+++ b/src/tests/containerizer/external_containerizer_test.cpp
@@ -0,0 +1,267 @@
+/**
+ * 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 <unistd.h>
+
+#include <gmock/gmock.h>
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include <mesos/resources.hpp>
+
+#include <process/future.hpp>
+
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+
+#include "master/master.hpp"
+#include "master/detector.hpp"
+
+#include "slave/flags.hpp"
+#include "slave/slave.hpp"
+
+#include "slave/containerizer/containerizer.hpp"
+#include "slave/containerizer/external_containerizer.hpp"
+
+#include "tests/mesos.hpp"
+#include "tests/flags.hpp"
+
+using namespace process;
+
+using mesos::internal::master::Master;
+using mesos::internal::slave::Containerizer;
+using mesos::internal::slave::Slave;
+
+using std::string;
+using std::vector;
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SaveArg;
+using testing::Invoke;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// The external containerizer tests currently rely on a Python script
+// which needs the Mesos Python egg being built.
+// TODO(tillt): Consider providing tests that do not rely on Python.
+#ifdef MESOS_HAS_PYTHON
+
+// TODO(tillt): Update and enhance the ExternalContainerizer tests,
+// possibly following some of the patterns used within the
+// IsolatorTests or even entirely reusing the Containerizer tests.
+class ExternalContainerizerTest : public MesosTest {};
+
+
+class MockExternalContainerizer : public slave::ExternalContainerizer
+{
+public:
+ MOCK_METHOD8(
+ launch,
+ process::Future<bool>(
+ const ContainerID&,
+ const TaskInfo&,
+ const ExecutorInfo&,
+ const std::string&,
+ const Option<std::string>&,
+ const SlaveID&,
+ const process::PID<slave::Slave>&,
+ bool checkpoint));
+
+ MockExternalContainerizer(const slave::Flags& flags)
+ : ExternalContainerizer(flags)
+ {
+ // Set up defaults for mocked methods.
+ // NOTE: See TestContainerizer::setup for why we use
+ // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
+ // 'ON_CALL' and 'WillByDefault'.
+ EXPECT_CALL(*this, launch(_, _, _, _, _, _, _, _))
+ .WillRepeatedly(Invoke(this, &MockExternalContainerizer::_launch));
+ }
+
+ process::Future<bool> _launch(
+ const ContainerID& containerId,
+ const TaskInfo& taskInfo,
+ const ExecutorInfo& executorInfo,
+ const string& directory,
+ const Option<string>& user,
+ const SlaveID& slaveId,
+ const PID<Slave>& slavePid,
+ bool checkpoint)
+ {
+ return slave::ExternalContainerizer::launch(
+ containerId,
+ taskInfo,
+ executorInfo,
+ directory,
+ user,
+ slaveId,
+ slavePid,
+ checkpoint);
+ }
+};
+
+
+// This test has been temporarily disabled due to MESOS-1257.
+TEST_F(ExternalContainerizerTest, DISABLED_Launch)
+{
+ Try<PID<Master> > master = this->StartMaster();
+ ASSERT_SOME(master);
+
+ Flags testFlags;
+
+ slave::Flags flags = this->CreateSlaveFlags();
+
+ flags.isolation = "external";
+ flags.containerizer_path =
+ testFlags.build_dir + "/src/examples/python/test-containerizer";
+
+ MockExternalContainerizer containerizer(flags);
+
+ Try<PID<Slave> > slave = this->StartSlave(&containerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+ AWAIT_READY(offers);
+
+ EXPECT_NE(0u, offers.get().size());
+
+ TaskInfo task;
+ task.set_name("isolator_test");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offers.get()[0].slave_id());
+ task.mutable_resources()->CopyFrom(offers.get()[0].resources());
+
+ Resources resources(offers.get()[0].resources());
+ Option<Bytes> mem = resources.mem();
+ ASSERT_SOME(mem);
+ Option<double> cpus = resources.cpus();
+ ASSERT_SOME(cpus);
+
+ const std::string& file = path::join(flags.work_dir, "ready");
+
+ // This task induces user/system load in a child process by
+ // running top in a child process for ten seconds.
+ task.mutable_command()->set_value(
+#ifdef __APPLE__
+ // Use logging mode with 30,000 samples with no interval.
+ "top -l 30000 -s 0 2>&1 > /dev/null & "
+#else
+ // Batch mode, with 30,000 samples with no interval.
+ "top -b -d 0 -n 30000 2>&1 > /dev/null & "
+#endif
+ "touch " + file + "; " // Signals that the top command is running.
+ "sleep 60");
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status))
+ .WillRepeatedly(Return()); // Ignore rest for now.
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(containerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&containerizer,
+ &MockExternalContainerizer::_launch)));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(containerId);
+
+ AWAIT_READY(status);
+
+ EXPECT_EQ(TASK_RUNNING, status.get().state());
+
+ // Wait for the task to begin inducing cpu time.
+ while (!os::exists(file));
+
+ ExecutorID executorId;
+ executorId.set_value(task.task_id().value());
+
+ // We'll wait up to 10 seconds for the child process to induce
+ // 1/8 of a second of user and system cpu time in total.
+ // TODO(bmahler): Also induce rss memory consumption, by re-using
+ // the balloon framework.
+ ResourceStatistics statistics;
+ Duration waited = Duration::zero();
+ do {
+ Future<ResourceStatistics> usage = containerizer.usage(containerId.get());
+ AWAIT_READY(usage);
+
+ statistics = usage.get();
+
+ // If we meet our usage expectations, we're done!
+ // NOTE: We are currently getting dummy-data from the test-
+ // containerizer python script matching these expectations.
+ // TODO(tillt): Consider working with real data.
+ if (statistics.cpus_user_time_secs() >= 0.120 &&
+ statistics.cpus_system_time_secs() >= 0.05 &&
+ statistics.mem_rss_bytes() >= 1024u) {
+ break;
+ }
+
+ os::sleep(Milliseconds(100));
+ waited += Milliseconds(100);
+ } while (waited < Seconds(10));
+
+ EXPECT_GE(statistics.cpus_user_time_secs(), 0.120);
+ EXPECT_GE(statistics.cpus_system_time_secs(), 0.05);
+ EXPECT_EQ(statistics.cpus_limit(), cpus.get());
+ EXPECT_GE(statistics.mem_rss_bytes(), 1024u);
+ EXPECT_EQ(statistics.mem_limit_bytes(), mem.get().bytes());
+
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status));
+
+ driver.killTask(task.task_id());
+
+ AWAIT_READY(status);
+
+ EXPECT_EQ(TASK_KILLED, status.get().state());
+
+ driver.stop();
+ driver.join();
+
+ this->Shutdown();
+}
+
+#endif // MESOS_HAS_PYTHON
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/fs_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/fs_tests.cpp b/src/tests/containerizer/fs_tests.cpp
new file mode 100644
index 0000000..34d3c41
--- /dev/null
+++ b/src/tests/containerizer/fs_tests.cpp
@@ -0,0 +1,170 @@
+/**
+ * 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 <paths.h>
+
+#include <gmock/gmock.h>
+
+#include <stout/foreach.hpp>
+#include <stout/gtest.hpp>
+#include <stout/none.hpp>
+#include <stout/option.hpp>
+#include <stout/try.hpp>
+
+#include "linux/fs.hpp"
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+using fs::MountTable;
+using fs::FileSystemTable;
+using fs::MountInfoTable;
+
+
+TEST(FsTest, MountTableRead)
+{
+ Try<MountTable> table = MountTable::read(_PATH_MOUNTED);
+
+ ASSERT_SOME(table);
+
+ Option<MountTable::Entry> root = None();
+ Option<MountTable::Entry> proc = None();
+ foreach (const MountTable::Entry& entry, table.get().entries) {
+ if (entry.dir == "/") {
+ root = entry;
+ } else if (entry.dir == "/proc") {
+ proc = entry;
+ }
+ }
+
+ EXPECT_SOME(root);
+ ASSERT_SOME(proc);
+ EXPECT_EQ(proc.get().type, "proc");
+}
+
+
+TEST(FsTest, MountTableHasOption)
+{
+ Try<MountTable> table = MountTable::read(_PATH_MOUNTED);
+
+ ASSERT_SOME(table);
+
+ Option<MountTable::Entry> proc = None();
+ foreach (const MountTable::Entry& entry, table.get().entries) {
+ if (entry.dir == "/proc") {
+ proc = entry;
+ }
+ }
+
+ ASSERT_SOME(proc);
+ EXPECT_TRUE(proc.get().hasOption(MNTOPT_RW));
+}
+
+
+TEST(FsTest, FileSystemTableRead)
+{
+ Try<FileSystemTable> table = FileSystemTable::read();
+
+ ASSERT_SOME(table);
+
+ // NOTE: We do not check for /proc because, it is not always present in
+ // /etc/fstab.
+ Option<FileSystemTable::Entry> root = None();
+ foreach (const FileSystemTable::Entry& entry, table.get().entries) {
+ if (entry.file == "/") {
+ root = entry;
+ }
+ }
+
+ EXPECT_SOME(root);
+}
+
+
+TEST(FsTest, MountInfoTableParse)
+{
+ // Parse a private mount (no optional fields).
+ const std::string privateMount =
+ "19 1 8:1 / / rw,relatime - ext4 /dev/sda1 rw,seclabel,data=ordered";
+ Try<MountInfoTable::Entry> entry = MountInfoTable::Entry::parse(privateMount);
+
+ ASSERT_SOME(entry);
+ EXPECT_EQ(19, entry.get().id);
+ EXPECT_EQ(1, entry.get().parent);
+ EXPECT_EQ(makedev(8, 1), entry.get().devno);
+ EXPECT_EQ("/", entry.get().root);
+ EXPECT_EQ("/", entry.get().target);
+ EXPECT_EQ("rw,relatime", entry.get().vfsOptions);
+ EXPECT_EQ("rw,seclabel,data=ordered", entry.get().fsOptions);
+ EXPECT_EQ("", entry.get().optionalFields);
+ EXPECT_EQ("ext4", entry.get().type);
+ EXPECT_EQ("/dev/sda1", entry.get().source);
+
+ // Parse a shared mount (includes one optional field).
+ const std::string sharedMount =
+ "19 1 8:1 / / rw,relatime shared:2 - ext4 /dev/sda1 rw,seclabel";
+ entry = MountInfoTable::Entry::parse(sharedMount);
+
+ ASSERT_SOME(entry);
+ EXPECT_EQ(19, entry.get().id);
+ EXPECT_EQ(1, entry.get().parent);
+ EXPECT_EQ(makedev(8, 1), entry.get().devno);
+ EXPECT_EQ("/", entry.get().root);
+ EXPECT_EQ("/", entry.get().target);
+ EXPECT_EQ("rw,relatime", entry.get().vfsOptions);
+ EXPECT_EQ("rw,seclabel", entry.get().fsOptions);
+ EXPECT_EQ("shared:2", entry.get().optionalFields);
+ EXPECT_EQ("ext4", entry.get().type);
+ EXPECT_EQ("/dev/sda1", entry.get().source);
+}
+
+
+TEST(FsTest, DISABLED_MountInfoTableRead)
+{
+ // Examine the calling process's mountinfo table.
+ Try<fs::MountInfoTable> table = fs::MountInfoTable::read();
+ ASSERT_SOME(table);
+
+ // Every system should have at least a rootfs mounted.
+ Option<MountInfoTable::Entry> root = None();
+ foreach (const MountInfoTable::Entry& entry, table.get().entries) {
+ if (entry.target == "/") {
+ root = entry;
+ }
+ }
+
+ EXPECT_SOME(root);
+
+ // Repeat for pid 1.
+ table = fs::MountInfoTable::read(1);
+ ASSERT_SOME(table);
+
+ // Every system should have at least a rootfs mounted.
+ root = None();
+ foreach (const MountInfoTable::Entry& entry, table.get().entries) {
+ if (entry.target == "/") {
+ root = entry;
+ }
+ }
+
+ EXPECT_SOME(root);
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/isolator.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/isolator.hpp b/src/tests/containerizer/isolator.hpp
new file mode 100644
index 0000000..8aaf88c
--- /dev/null
+++ b/src/tests/containerizer/isolator.hpp
@@ -0,0 +1,101 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TEST_ISOLATOR_HPP__
+#define __TEST_ISOLATOR_HPP__
+
+#include <gmock/gmock.h>
+
+#include "slave/containerizer/isolator.hpp"
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class TestIsolatorProcess : public slave::MesosIsolatorProcess
+{
+public:
+ static Try<mesos::slave::Isolator*> create(
+ const Option<CommandInfo>& commandInfo)
+ {
+ process::Owned<MesosIsolatorProcess> process(
+ new TestIsolatorProcess(commandInfo));
+
+ return new slave::MesosIsolator(process);
+ }
+
+ MOCK_METHOD2(
+ recover,
+ process::Future<Nothing>(
+ const std::list<mesos::slave::ExecutorRunState>&,
+ const hashset<ContainerID>&));
+
+ virtual process::Future<Option<CommandInfo>> prepare(
+ const ContainerID& containerId,
+ const ExecutorInfo& executorInfo,
+ const std::string& directory,
+ const Option<std::string>& rootfs,
+ const Option<std::string>& user)
+ {
+ return commandInfo;
+ }
+
+ MOCK_METHOD2(
+ isolate,
+ process::Future<Nothing>(const ContainerID&, pid_t));
+
+ MOCK_METHOD1(
+ watch,
+ process::Future<mesos::slave::ExecutorLimitation>(const ContainerID&));
+
+ MOCK_METHOD2(
+ update,
+ process::Future<Nothing>(const ContainerID&, const Resources&));
+
+ MOCK_METHOD1(
+ usage,
+ process::Future<ResourceStatistics>(const ContainerID&));
+
+ MOCK_METHOD1(
+ cleanup,
+ process::Future<Nothing>(const ContainerID&));
+
+private:
+ TestIsolatorProcess(const Option<CommandInfo>& _commandInfo)
+ : commandInfo(_commandInfo)
+ {
+ EXPECT_CALL(*this, watch(testing::_))
+ .WillRepeatedly(testing::Return(promise.future()));
+
+ EXPECT_CALL(*this, isolate(testing::_, testing::_))
+ .WillRepeatedly(testing::Return(Nothing()));
+
+ EXPECT_CALL(*this, cleanup(testing::_))
+ .WillRepeatedly(testing::Return(Nothing()));
+ }
+
+ const Option<CommandInfo> commandInfo;
+
+ process::Promise<mesos::slave::ExecutorLimitation> promise;
+};
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
+
+#endif // __TEST_ISOLATOR_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/isolator_tests.cpp b/src/tests/containerizer/isolator_tests.cpp
new file mode 100644
index 0000000..59f08c0
--- /dev/null
+++ b/src/tests/containerizer/isolator_tests.cpp
@@ -0,0 +1,1317 @@
+/**
+ * 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 <unistd.h>
+
+#include <gmock/gmock.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <mesos/resources.hpp>
+
+#include <mesos/module/isolator.hpp>
+
+#include <mesos/slave/isolator.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+#include <process/reap.hpp>
+
+#include <stout/abort.hpp>
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+#include <stout/path.hpp>
+
+#ifdef __linux__
+#include "linux/ns.hpp"
+#endif // __linux__
+
+#include "master/master.hpp"
+#include "master/detector.hpp"
+
+#include "slave/flags.hpp"
+#include "slave/slave.hpp"
+
+#ifdef __linux__
+#include "slave/containerizer/isolators/cgroups/constants.hpp"
+#include "slave/containerizer/isolators/cgroups/cpushare.hpp"
+#include "slave/containerizer/isolators/cgroups/mem.hpp"
+#include "slave/containerizer/isolators/cgroups/perf_event.hpp"
+#include "slave/containerizer/isolators/filesystem/shared.hpp"
+#endif // __linux__
+#include "slave/containerizer/isolators/posix.hpp"
+
+#include "slave/containerizer/launcher.hpp"
+#ifdef __linux__
+#include "slave/containerizer/fetcher.hpp"
+#include "slave/containerizer/linux_launcher.hpp"
+
+#include "slave/containerizer/mesos/containerizer.hpp"
+#include "slave/containerizer/mesos/launch.hpp"
+#endif // __linux__
+
+#include "tests/flags.hpp"
+#include "tests/mesos.hpp"
+#include "tests/module.hpp"
+#include "tests/utils.hpp"
+
+#include "tests/containerizer/memory_test_helper.hpp"
+
+using namespace process;
+
+using mesos::internal::master::Master;
+#ifdef __linux__
+using mesos::internal::slave::CgroupsCpushareIsolatorProcess;
+using mesos::internal::slave::CgroupsMemIsolatorProcess;
+using mesos::internal::slave::CgroupsPerfEventIsolatorProcess;
+using mesos::internal::slave::CPU_SHARES_PER_CPU_REVOCABLE;
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::LinuxLauncher;
+using mesos::internal::slave::SharedFilesystemIsolatorProcess;
+#endif // __linux__
+using mesos::internal::slave::Launcher;
+using mesos::internal::slave::MesosContainerizer;
+using mesos::internal::slave::PosixLauncher;
+using mesos::internal::slave::PosixCpuIsolatorProcess;
+using mesos::internal::slave::PosixMemIsolatorProcess;
+using mesos::internal::slave::Slave;
+
+using mesos::slave::Isolator;
+using mesos::slave::IsolatorProcess;
+
+using std::ostringstream;
+using std::set;
+using std::string;
+using std::vector;
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SaveArg;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+static int childSetup(int pipes[2])
+{
+ // In child process.
+ while (::close(pipes[1]) == -1 && errno == EINTR);
+
+ // Wait until the parent signals us to continue.
+ char dummy;
+ ssize_t length;
+ while ((length = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
+ errno == EINTR);
+
+ if (length != sizeof(dummy)) {
+ ABORT("Failed to synchronize with parent");
+ }
+
+ while (::close(pipes[0]) == -1 && errno == EINTR);
+
+ return 0;
+}
+
+
+template <typename T>
+class CpuIsolatorTest : public MesosTest {};
+
+
+typedef ::testing::Types<
+ PosixCpuIsolatorProcess,
+#ifdef __linux__
+ CgroupsCpushareIsolatorProcess,
+#endif // __linux__
+ tests::Module<Isolator, TestCpuIsolator>> CpuIsolatorTypes;
+
+
+TYPED_TEST_CASE(CpuIsolatorTest, CpuIsolatorTypes);
+
+
+TYPED_TEST(CpuIsolatorTest, UserCpuUsage)
+{
+ 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("cpus:1.0").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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ const string& file = path::join(dir.get(), "mesos_isolator_test_ready");
+
+ // Max out a single core in userspace. This will run for at most one second.
+ string command = "while true ; do true ; done &"
+ "touch " + file + "; " // Signals the command is running.
+ "sleep 60";
+
+ int pipes[2];
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ vector<string> argv(3);
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = command;
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ lambda::bind(&childSetup, pipes));
+
+ ASSERT_SOME(pid);
+
+ // Reap 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 for the command to start.
+ while (!os::exists(file));
+
+ // Wait up to 1 second for the child process to induce 1/8 of a second of
+ // user cpu time.
+ ResourceStatistics statistics;
+ 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.cpus_user_time_secs() >= 0.125) {
+ break;
+ }
+
+ os::sleep(Milliseconds(200));
+ waited += Milliseconds(200);
+ } while (waited < Seconds(1));
+
+ EXPECT_LE(0.125, statistics.cpus_user_time_secs());
+
+ // 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();
+}
+
+
+TYPED_TEST(CpuIsolatorTest, SystemCpuUsage)
+{
+ 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("cpus:1.0").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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ const string& file = path::join(dir.get(), "mesos_isolator_test_ready");
+
+ // Generating random numbers is done by the kernel and will max out a single
+ // core and run almost exclusively in the kernel, i.e., system time.
+ string command = "cat /dev/urandom > /dev/null & "
+ "touch " + file + "; " // Signals the command is running.
+ "sleep 60";
+
+ int pipes[2];
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ vector<string> argv(3);
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = command;
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ lambda::bind(&childSetup, pipes));
+
+ ASSERT_SOME(pid);
+
+ // Reap 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 for the command to start.
+ while (!os::exists(file));
+
+ // Wait up to 1 second for the child process to induce 1/8 of a second of
+ // system cpu time.
+ ResourceStatistics statistics;
+ 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.cpus_system_time_secs() >= 0.125) {
+ break;
+ }
+
+ os::sleep(Milliseconds(200));
+ waited += Milliseconds(200);
+ } while (waited < Seconds(1));
+
+ EXPECT_LE(0.125, statistics.cpus_system_time_secs());
+
+ // 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();
+}
+
+
+#ifdef __linux__
+class RevocableCpuIsolatorTest : public MesosTest {};
+
+
+TEST_F(RevocableCpuIsolatorTest, ROOT_CGROUPS_RevocableCpu)
+{
+ slave::Flags flags;
+
+ Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher = PosixLauncher::create(flags);
+
+ // Include revocable CPU in the executor's resources.
+ Resource cpu = Resources::parse("cpus", "1", "*").get();
+ cpu.mutable_revocable();
+
+ ExecutorInfo executorInfo;
+ executorInfo.add_resources()->CopyFrom(cpu);
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ os::getcwd(),
+ None(),
+ None()));
+
+ vector<string> argv{"sleep", "100"};
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sleep",
+ argv,
+ Subprocess::PATH("/dev/null"),
+ Subprocess::PATH("/dev/null"),
+ Subprocess::PATH("/dev/null"),
+ None(),
+ None(),
+ None());
+
+ ASSERT_SOME(pid);
+
+ AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));
+
+ // Executor should have proper cpu.shares for revocable containers.
+ Result<string> cpuHierarchy = cgroups::hierarchy("cpu");
+ ASSERT_SOME(cpuHierarchy);
+
+ Result<string> cpuCgroup = cgroups::cpu::cgroup(pid.get());
+ ASSERT_SOME(cpuCgroup);
+
+ EXPECT_SOME_EQ(
+ CPU_SHARES_PER_CPU_REVOCABLE,
+ cgroups::cpu::shares(cpuHierarchy.get(), cpuCgroup.get()));
+
+ // Kill the container and clean up.
+ Future<Option<int>> status = process::reap(pid.get());
+
+ AWAIT_READY(launcher.get()->destroy(containerId));
+
+ AWAIT_READY(status);
+
+ AWAIT_READY(isolator.get()->cleanup(containerId));
+
+ delete isolator.get();
+ delete launcher.get();
+}
+#endif // __linux__
+
+
+#ifdef __linux__
+class LimitedCpuIsolatorTest : public MesosTest {};
+
+
+TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs)
+{
+ slave::Flags flags;
+
+ // Enable CFS to cap CPU utilization.
+ flags.cgroups_enable_cfs = true;
+
+ Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher =
+ LinuxLauncher::create(flags, isolator.get()->namespaces().get());
+ CHECK_SOME(launcher);
+
+ // Set the executor's resources to 0.5 cpu.
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_resources()->CopyFrom(
+ Resources::parse("cpus:0.5").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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ // Generate random numbers to max out a single core. We'll run this for 0.5
+ // seconds of wall time so it should consume approximately 250 ms of total
+ // cpu time when limited to 0.5 cpu. We use /dev/urandom to prevent blocking
+ // on Linux when there's insufficient entropy.
+ string command = "cat /dev/urandom > /dev/null & "
+ "export MESOS_TEST_PID=$! && "
+ "sleep 0.5 && "
+ "kill $MESOS_TEST_PID";
+
+ int pipes[2];
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ vector<string> argv(3);
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = command;
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ lambda::bind(&childSetup, pipes));
+
+ ASSERT_SOME(pid);
+
+ // Reap 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 for the command to complete.
+ AWAIT_READY(status);
+
+ Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+
+ // Expect that no more than 300 ms of cpu time has been consumed. We also
+ // check that at least 50 ms of cpu time has been consumed so this test will
+ // fail if the host system is very heavily loaded. This behavior is correct
+ // because under such conditions we aren't actually testing the CFS cpu
+ // limiter.
+ double cpuTime = usage.get().cpus_system_time_secs() +
+ usage.get().cpus_user_time_secs();
+
+ EXPECT_GE(0.30, cpuTime);
+ EXPECT_LE(0.05, cpuTime);
+
+ // 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();
+}
+
+
+// This test verifies that we can successfully launch a container with
+// a big (>= 10 cpus) cpu quota. This is to catch the regression
+// observed in MESOS-1049.
+// TODO(vinod): Revisit this if/when the isolator restricts the number
+// of cpus that an executor can use based on the slave cpus.
+TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Cfs_Big_Quota)
+{
+ slave::Flags flags;
+
+ // Enable CFS to cap CPU utilization.
+ flags.cgroups_enable_cfs = true;
+
+ Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher =
+ LinuxLauncher::create(flags, isolator.get()->namespaces().get());
+ CHECK_SOME(launcher);
+
+ // Set the executor's resources to 100.5 cpu.
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_resources()->CopyFrom(
+ Resources::parse("cpus:100.5").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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ int pipes[2];
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ vector<string> argv(3);
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = "exit 0";
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ lambda::bind(&childSetup, pipes));
+
+ ASSERT_SOME(pid);
+
+ // Reap 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 for the command to complete successfully.
+ AWAIT_READY(status);
+ ASSERT_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();
+}
+
+
+// A test to verify the number of processes and threads in a
+// container.
+TEST_F(LimitedCpuIsolatorTest, ROOT_CGROUPS_Pids_and_Tids)
+{
+ slave::Flags flags;
+ flags.cgroups_cpu_enable_pids_and_tids_count = true;
+
+ Try<Isolator*> isolator = CgroupsCpushareIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher =
+ LinuxLauncher::create(flags, isolator.get()->namespaces().get());
+ CHECK_SOME(launcher);
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_resources()->CopyFrom(
+ Resources::parse("cpus:0.5;mem:512").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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ // Right after the creation of the cgroup, which happens in
+ // 'prepare', we check that it is empty.
+ Future<ResourceStatistics> usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+ EXPECT_EQ(0U, usage.get().processes());
+ EXPECT_EQ(0U, usage.get().threads());
+
+ int pipes[2];
+ ASSERT_NE(-1, ::pipe(pipes));
+
+ vector<string> argv(3);
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = "while true; do sleep 1; done;";
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ argv,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ lambda::bind(&childSetup, pipes));
+
+ ASSERT_SOME(pid);
+
+ // Reap the forked child.
+ Future<Option<int>> status = process::reap(pid.get());
+
+ // Continue in the parent.
+ ASSERT_SOME(os::close(pipes[0]));
+
+ // Before isolation, the cgroup is empty.
+ usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+ EXPECT_EQ(0U, usage.get().processes());
+ EXPECT_EQ(0U, usage.get().threads());
+
+ // Isolate the forked child.
+ AWAIT_READY(isolator.get()->isolate(containerId, pid.get()));
+
+ // After the isolation, the cgroup is not empty, even though the
+ // process hasn't exec'd yet.
+ usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+ EXPECT_EQ(1U, usage.get().processes());
+ EXPECT_EQ(1U, usage.get().threads());
+
+ // Now signal the child to continue.
+ char dummy;
+ ASSERT_LT(0, ::write(pipes[1], &dummy, sizeof(dummy)));
+
+ ASSERT_SOME(os::close(pipes[1]));
+
+ // Process count should be 1 since 'sleep' is still sleeping.
+ usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+ EXPECT_EQ(1U, usage.get().processes());
+ EXPECT_EQ(1U, usage.get().threads());
+
+ // Ensure all processes are killed.
+ AWAIT_READY(launcher.get()->destroy(containerId));
+
+ // Wait for the command to complete.
+ AWAIT_READY(status);
+
+ // After the process is killed, the cgroup should be empty again.
+ usage = isolator.get()->usage(containerId);
+ AWAIT_READY(usage);
+ EXPECT_EQ(0U, usage.get().processes());
+ EXPECT_EQ(0U, usage.get().threads());
+
+ // Let the isolator clean up.
+ AWAIT_READY(isolator.get()->cleanup(containerId));
+
+ delete isolator.get();
+ delete launcher.get();
+}
+#endif // __linux__
+
+
+template <typename T>
+class MemIsolatorTest : public MesosTest {};
+
+
+typedef ::testing::Types<
+ PosixMemIsolatorProcess,
+#ifdef __linux__
+ CgroupsMemIsolatorProcess,
+#endif // __linux__
+ tests::Module<Isolator, TestMemIsolator>> MemIsolatorTypes;
+
+
+TYPED_TEST_CASE(MemIsolatorTest, MemIsolatorTypes);
+
+
+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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ 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();
+}
+
+
+#ifdef __linux__
+class PerfEventIsolatorTest : public MesosTest {};
+
+
+TEST_F(PerfEventIsolatorTest, ROOT_CGROUPS_Sample)
+{
+ slave::Flags flags;
+
+ flags.perf_events = "cycles,task-clock";
+ flags.perf_duration = Milliseconds(250);
+ flags.perf_interval = Milliseconds(500);
+
+ Try<Isolator*> isolator = CgroupsPerfEventIsolatorProcess::create(flags);
+ ASSERT_SOME(isolator);
+
+ ExecutorInfo executorInfo;
+
+ 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);
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ dir.get(),
+ None(),
+ None()));
+
+ // This first sample is likely to be empty because perf hasn't
+ // completed yet but we should still have the required fields.
+ Future<ResourceStatistics> statistics1 = isolator.get()->usage(containerId);
+ AWAIT_READY(statistics1);
+ ASSERT_TRUE(statistics1.get().has_perf());
+ EXPECT_TRUE(statistics1.get().perf().has_timestamp());
+ EXPECT_TRUE(statistics1.get().perf().has_duration());
+
+ // Wait until we get the next sample. We use a generous timeout of
+ // two seconds because we currently have a one second reap interval;
+ // when running perf with perf_duration of 250ms we won't notice the
+ // exit for up to one second.
+ ResourceStatistics statistics2;
+ Duration waited = Duration::zero();
+ do {
+ Future<ResourceStatistics> statistics = isolator.get()->usage(containerId);
+ AWAIT_READY(statistics);
+
+ statistics2 = statistics.get();
+
+ ASSERT_TRUE(statistics2.has_perf());
+
+ if (statistics1.get().perf().timestamp() !=
+ statistics2.perf().timestamp()) {
+ break;
+ }
+
+ os::sleep(Milliseconds(250));
+ waited += Milliseconds(250);
+ } while (waited < Seconds(2));
+
+ sleep(2);
+
+ EXPECT_NE(statistics1.get().perf().timestamp(),
+ statistics2.perf().timestamp());
+
+ EXPECT_TRUE(statistics2.perf().has_cycles());
+ EXPECT_LE(0u, statistics2.perf().cycles());
+
+ EXPECT_TRUE(statistics2.perf().has_task_clock());
+ EXPECT_LE(0.0, statistics2.perf().task_clock());
+
+ AWAIT_READY(isolator.get()->cleanup(containerId));
+
+ delete isolator.get();
+}
+
+
+class SharedFilesystemIsolatorTest : public MesosTest {};
+
+
+// Test that a container can create a private view of a system
+// directory (/var/tmp). Check that a file written by a process inside
+// the container doesn't appear on the host filesystem but does appear
+// under the container's work directory.
+// This test is disabled since we're planning to remove the shared
+// filesystem isolator and this test is not working on other distros
+// such as CentOS 7.1
+// TODO(tnachen): Remove this test when shared filesystem isolator
+// is removed.
+TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_RelativeVolume)
+{
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/shared";
+
+ Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher =
+ LinuxLauncher::create(flags, isolator.get()->namespaces().get());
+ CHECK_SOME(launcher);
+
+ // Use /var/tmp so we don't mask the work directory (under /tmp).
+ const string containerPath = "/var/tmp";
+ ASSERT_TRUE(os::stat::isdir(containerPath));
+
+ // Use a host path relative to the container work directory.
+ const string hostPath = strings::remove(containerPath, "/", strings::PREFIX);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::MESOS);
+ containerInfo.add_volumes()->CopyFrom(
+ CREATE_VOLUME(containerPath, hostPath, Volume::RW));
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ Future<Option<CommandInfo> > prepare =
+ isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ flags.work_dir,
+ None(),
+ None());
+
+ AWAIT_READY(prepare);
+ ASSERT_SOME(prepare.get());
+
+ // The test will touch a file in container path.
+ const string file = path::join(containerPath, UUID::random().toString());
+ ASSERT_FALSE(os::exists(file));
+
+ // Manually run the isolator's preparation command first, then touch
+ // the file.
+ vector<string> args;
+ args.push_back("/bin/sh");
+ args.push_back("-x");
+ args.push_back("-c");
+ args.push_back(prepare.get().get().value() + " && touch " + file);
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ args,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ None());
+ ASSERT_SOME(pid);
+
+ // Set up the reaper to wait on the forked child.
+ Future<Option<int> > status = process::reap(pid.get());
+
+ AWAIT_READY(status);
+ EXPECT_SOME_EQ(0, status.get());
+
+ // Check the correct hierarchy was created under the container work
+ // directory.
+ string dir = "/";
+ foreach (const string& subdir, strings::tokenize(containerPath, "/")) {
+ dir = path::join(dir, subdir);
+
+ struct stat hostStat;
+ EXPECT_EQ(0, ::stat(dir.c_str(), &hostStat));
+
+ struct stat containerStat;
+ EXPECT_EQ(0,
+ ::stat(path::join(flags.work_dir, dir).c_str(), &containerStat));
+
+ EXPECT_EQ(hostStat.st_mode, containerStat.st_mode);
+ EXPECT_EQ(hostStat.st_uid, containerStat.st_uid);
+ EXPECT_EQ(hostStat.st_gid, containerStat.st_gid);
+ }
+
+ // Check it did *not* create a file in the host namespace.
+ EXPECT_FALSE(os::exists(file));
+
+ // Check it did create the file under the container's work directory
+ // on the host.
+ EXPECT_TRUE(os::exists(path::join(flags.work_dir, file)));
+
+ delete launcher.get();
+ delete isolator.get();
+}
+
+
+// This test is disabled since we're planning to remove the shared
+// filesystem isolator and this test is not working on other distros
+// such as CentOS 7.1
+// TODO(tnachen): Remove this test when shared filesystem isolator
+// is removed.
+TEST_F(SharedFilesystemIsolatorTest, DISABLED_ROOT_AbsoluteVolume)
+{
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/shared";
+
+ Try<Isolator*> isolator = SharedFilesystemIsolatorProcess::create(flags);
+ CHECK_SOME(isolator);
+
+ Try<Launcher*> launcher =
+ LinuxLauncher::create(flags, isolator.get()->namespaces().get());
+ CHECK_SOME(launcher);
+
+ // We'll mount the absolute test work directory as /var/tmp in the
+ // container.
+ const string hostPath = flags.work_dir;
+ const string containerPath = "/var/tmp";
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::MESOS);
+ containerInfo.add_volumes()->CopyFrom(
+ CREATE_VOLUME(containerPath, hostPath, Volume::RW));
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ Future<Option<CommandInfo> > prepare =
+ isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ flags.work_dir,
+ None(),
+ None());
+
+ AWAIT_READY(prepare);
+ ASSERT_SOME(prepare.get());
+
+ // Test the volume mounting by touching a file in the container's
+ // /tmp, which should then be in flags.work_dir.
+ const string filename = UUID::random().toString();
+ ASSERT_FALSE(os::exists(path::join(containerPath, filename)));
+
+ vector<string> args;
+ args.push_back("/bin/sh");
+ args.push_back("-x");
+ args.push_back("-c");
+ args.push_back(prepare.get().get().value() +
+ " && touch " +
+ path::join(containerPath, filename));
+
+ Try<pid_t> pid = launcher.get()->fork(
+ containerId,
+ "/bin/sh",
+ args,
+ Subprocess::FD(STDIN_FILENO),
+ Subprocess::FD(STDOUT_FILENO),
+ Subprocess::FD(STDERR_FILENO),
+ None(),
+ None(),
+ None());
+ ASSERT_SOME(pid);
+
+ // Set up the reaper to wait on the forked child.
+ Future<Option<int> > status = process::reap(pid.get());
+
+ AWAIT_READY(status);
+ EXPECT_SOME_EQ(0, status.get());
+
+ // Check the file was created in flags.work_dir.
+ EXPECT_TRUE(os::exists(path::join(hostPath, filename)));
+
+ // Check it didn't get created in the host's view of containerPath.
+ EXPECT_FALSE(os::exists(path::join(containerPath, filename)));
+
+ delete launcher.get();
+ delete isolator.get();
+}
+
+
+class NamespacesPidIsolatorTest : public MesosTest {};
+
+
+TEST_F(NamespacesPidIsolatorTest, ROOT_PidNamespace)
+{
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "namespaces/pid";
+
+ string directory = os::getcwd(); // We're inside a temporary sandbox.
+
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer =
+ MesosContainerizer::create(flags, false, &fetcher);
+ ASSERT_SOME(containerizer);
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ // Write the command's pid namespace inode and init name to files.
+ const string command =
+ "stat -c %i /proc/self/ns/pid > ns && (cat /proc/1/comm > init)";
+
+ process::Future<bool> launch = containerizer.get()->launch(
+ containerId,
+ CREATE_EXECUTOR_INFO("executor", command),
+ directory,
+ None(),
+ SlaveID(),
+ process::PID<Slave>(),
+ false);
+ AWAIT_READY(launch);
+ ASSERT_TRUE(launch.get());
+
+ // Wait on the container.
+ process::Future<containerizer::Termination> wait =
+ containerizer.get()->wait(containerId);
+ AWAIT_READY(wait);
+
+ // Check the executor exited correctly.
+ EXPECT_TRUE(wait.get().has_status());
+ EXPECT_EQ(0, wait.get().status());
+
+ // Check that the command was run in a different pid namespace.
+ Try<ino_t> testPidNamespace = ns::getns(::getpid(), "pid");
+ ASSERT_SOME(testPidNamespace);
+
+ Try<string> containerPidNamespace = os::read(path::join(directory, "ns"));
+ ASSERT_SOME(containerPidNamespace);
+
+ EXPECT_NE(stringify(testPidNamespace.get()),
+ strings::trim(containerPidNamespace.get()));
+
+ // Check that 'sh' is the container's 'init' process.
+ // This verifies that /proc has been correctly mounted for the container.
+ Try<string> init = os::read(path::join(directory, "init"));
+ ASSERT_SOME(init);
+
+ EXPECT_EQ("sh", strings::trim(init.get()));
+
+ delete containerizer.get();
+}
+
+
+// Username for the unprivileged user that will be created to test
+// unprivileged cgroup creation. It will be removed after the tests.
+// It is presumed this user does not normally exist.
+const string UNPRIVILEGED_USERNAME = "mesos.test.unprivileged.user";
+
+
+template <typename T>
+class UserCgroupIsolatorTest : public MesosTest
+{
+public:
+ static void SetUpTestCase()
+ {
+ // Remove the user in case it wasn't cleaned up from a previous
+ // test.
+ os::system("userdel -r " + UNPRIVILEGED_USERNAME + " > /dev/null");
+
+ ASSERT_EQ(0, os::system("useradd " + UNPRIVILEGED_USERNAME));
+ }
+
+
+ static void TearDownTestCase()
+ {
+ ASSERT_EQ(0, os::system("userdel -r " + UNPRIVILEGED_USERNAME));
+ }
+};
+
+
+// Test all isolators that use cgroups.
+typedef ::testing::Types<
+ CgroupsMemIsolatorProcess,
+ CgroupsCpushareIsolatorProcess,
+ CgroupsPerfEventIsolatorProcess> CgroupsIsolatorTypes;
+
+
+TYPED_TEST_CASE(UserCgroupIsolatorTest, CgroupsIsolatorTypes);
+
+
+TYPED_TEST(UserCgroupIsolatorTest, ROOT_CGROUPS_UserCgroup)
+{
+ slave::Flags flags;
+ flags.perf_events = "cpu-cycles"; // Needed for CgroupsPerfEventIsolator.
+
+ Try<Isolator*> isolator = TypeParam::create(flags);
+ ASSERT_SOME(isolator);
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_resources()->CopyFrom(
+ Resources::parse("mem:1024;cpus:1").get()); // For cpu/mem isolators.
+
+ ContainerID containerId;
+ containerId.set_value(UUID::random().toString());
+
+ AWAIT_READY(isolator.get()->prepare(
+ containerId,
+ executorInfo,
+ os::getcwd(),
+ None(),
+ UNPRIVILEGED_USERNAME));
+
+ // Isolators don't provide a way to determine the cgroups they use
+ // so we'll inspect the cgroups for an isolated dummy process.
+ pid_t pid = fork();
+ if (pid == 0) {
+ // Child just sleeps.
+ ::sleep(100);
+
+ ABORT("Child process should not reach here");
+ }
+ ASSERT_GT(pid, 0);
+
+ AWAIT_READY(isolator.get()->isolate(containerId, pid));
+
+ // Get the container's cgroups from /proc/$PID/cgroup. We're only
+ // interested in the cgroups that this isolator has created which we
+ // can do explicitly by selecting those that have the path that
+ // corresponds to the 'cgroups_root' slave flag. For example:
+ //
+ // $ cat /proc/pid/cgroup
+ // 6:blkio:/
+ // 5:perf_event:/
+ // 4:memory:/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c
+ // 3:freezer:/
+ // 2:cpuacct:/
+ // 1:cpu:/
+ //
+ // Our 'grep' will only select the 'memory' line and then 'awk' will
+ // output 'memory/mesos/b7410ed8-c85b-445e-b50e-3a1698d0e18c'.
+ ostringstream output;
+ Try<int> status = os::shell(
+ &output,
+ "grep '" + path::join("/", flags.cgroups_root) + "' /proc/" +
+ stringify(pid) + "/cgroup | awk -F ':' '{print $2$3}'");
+
+ ASSERT_SOME(status);
+
+ // Kill the dummy child process.
+ ::kill(pid, SIGKILL);
+ int exitStatus;
+ EXPECT_NE(-1, ::waitpid(pid, &exitStatus, 0));
+
+ vector<string> cgroups = strings::tokenize(output.str(), "\n");
+ ASSERT_FALSE(cgroups.empty());
+
+ foreach (const string& cgroup, cgroups) {
+ // Check the user cannot manipulate the container's cgroup control
+ // files.
+ EXPECT_NE(0, os::system(
+ "su - " + UNPRIVILEGED_USERNAME +
+ " -c 'echo $$ >" +
+ path::join(flags.cgroups_hierarchy, cgroup, "cgroup.procs") +
+ "'"));
+
+ // Check the user can create a cgroup under the container's
+ // cgroup.
+ string userCgroup = path::join(cgroup, "user");
+
+ EXPECT_EQ(0, os::system(
+ "su - " +
+ UNPRIVILEGED_USERNAME +
+ " -c 'mkdir " +
+ path::join(flags.cgroups_hierarchy, userCgroup) +
+ "'"));
+
+ // Check the user can manipulate control files in the created
+ // cgroup.
+ EXPECT_EQ(0, os::system(
+ "su - " +
+ UNPRIVILEGED_USERNAME +
+ " -c 'echo $$ >" +
+ path::join(flags.cgroups_hierarchy, userCgroup, "cgroup.procs") +
+ "'"));
+ }
+
+ // Clean up the container. This will also remove the nested cgroups.
+ AWAIT_READY(isolator.get()->cleanup(containerId));
+
+ delete isolator.get();
+}
+#endif // __linux__
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/launch_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/launch_tests.cpp b/src/tests/containerizer/launch_tests.cpp
new file mode 100644
index 0000000..73c8c5f
--- /dev/null
+++ b/src/tests/containerizer/launch_tests.cpp
@@ -0,0 +1,238 @@
+/**
+ * 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 <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include <stout/foreach.hpp>
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+#include <stout/try.hpp>
+
+#include <process/gtest.hpp>
+#include <process/io.hpp>
+#include <process/reap.hpp>
+#include <process/subprocess.hpp>
+
+#include "mesos/resources.hpp"
+
+#include "slave/containerizer/mesos/launch.hpp"
+
+#include "linux/fs.hpp"
+
+#include "tests/flags.hpp"
+#include "tests/utils.hpp"
+
+using namespace process;
+
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class Chroot
+{
+public:
+ Chroot(const string& _rootfs)
+ : rootfs(_rootfs) {}
+
+ virtual ~Chroot() {}
+
+ virtual Try<Subprocess> run(const string& command) = 0;
+
+ const string rootfs;
+};
+
+
+class BasicLinuxChroot : public Chroot
+{
+public:
+ static Try<Owned<Chroot>> create(const string& rootfs)
+ {
+ if (!os::exists(rootfs)) {
+ return Error("rootfs does not exist");
+ }
+
+ if (os::system("cp -r /bin " + rootfs + "/") != 0) {
+ return ErrnoError("Failed to copy /bin to chroot");
+ }
+
+ if (os::system("cp -r /lib " + rootfs + "/") != 0) {
+ return ErrnoError("Failed to copy /lib to chroot");
+ }
+
+ if (os::system("cp -r /lib64 " + rootfs + "/") != 0) {
+ return ErrnoError("Failed to copy /lib64 to chroot");
+ }
+
+ vector<string> directories = {"proc", "sys", "dev", "tmp"};
+ foreach (const string& directory, directories) {
+ Try<Nothing> mkdir = os::mkdir(path::join(rootfs, directory));
+ if (mkdir.isError()) {
+ return Error("Failed to create /" + directory + " in chroot: " +
+ mkdir.error());
+ }
+ }
+
+ // We need to bind mount the rootfs so we can pivot on it.
+ Try<Nothing> mount =
+ fs::mount(rootfs, rootfs, None(), MS_BIND | MS_SLAVE, NULL);
+
+ if (mount.isError()) {
+ return Error("Failed to bind mount chroot rootfs: " + mount.error());
+ }
+
+ return Owned<Chroot>(new BasicLinuxChroot(rootfs));
+ }
+
+ virtual Try<Subprocess> run(const string& _command)
+ {
+ slave::MesosContainerizerLaunch::Flags launchFlags;
+
+ CommandInfo command;
+ command.set_value(_command);
+
+ launchFlags.command = JSON::Protobuf(command);
+ launchFlags.directory = "/tmp";
+ launchFlags.pipe_read = open("/dev/zero", O_RDONLY);
+ launchFlags.pipe_write = open("/dev/null", O_WRONLY);
+ launchFlags.rootfs = rootfs;
+
+ vector<string> argv(2);
+ argv[0] = "mesos-containerizer";
+ argv[1] = slave::MesosContainerizerLaunch::NAME;
+
+ Try<Subprocess> s = subprocess(
+ path::join(tests::flags.build_dir, "src", "mesos-containerizer"),
+ argv,
+ Subprocess::PATH("/dev/null"),
+ Subprocess::PIPE(),
+ Subprocess::FD(STDERR_FILENO),
+ launchFlags,
+ None(),
+ None(),
+ lambda::bind(&clone, lambda::_1));
+
+ if (s.isError()) {
+ close(launchFlags.pipe_read.get());
+ close(launchFlags.pipe_write.get());
+ } else {
+ s.get().status().onAny([=]() {
+ // Close when the subprocess terminates.
+ close(launchFlags.pipe_read.get());
+ close(launchFlags.pipe_write.get());
+ });
+ }
+
+ return s;
+ }
+
+private:
+ static pid_t clone(const lambda::function<int()>& f)
+ {
+ static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+ return ::clone(
+ _clone,
+ &stack[sizeof(stack)/sizeof(stack[0]) - 1], // Stack grows down.
+ CLONE_NEWNS | SIGCHLD, // Specify SIGCHLD as child termination signal.
+ (void*) &f);
+ }
+
+ static int _clone(void* f)
+ {
+ const lambda::function<int()>* _f =
+ static_cast<const lambda::function<int()>*> (f);
+
+ return (*_f)();
+ }
+
+ BasicLinuxChroot(const string& rootfs) : Chroot(rootfs) {}
+
+ ~BasicLinuxChroot()
+ {
+ // Because the test process has the rootfs as its cwd the umount
+ // won't actually happen until the
+ // TemporaryDirectoryTest::TearDown() changes back to the original
+ // directory.
+ fs::unmount(rootfs, MNT_DETACH);
+ }
+};
+
+
+template <typename T>
+class LaunchChrootTest : public TemporaryDirectoryTest {};
+
+
+// TODO(idownes): Add tests for OSX chroots.
+typedef ::testing::Types<BasicLinuxChroot> ChrootTypes;
+
+
+TYPED_TEST_CASE(LaunchChrootTest, ChrootTypes);
+
+
+TYPED_TEST(LaunchChrootTest, ROOT_DifferentRoot)
+{
+ Try<Owned<Chroot>> chroot = TypeParam::create(os::getcwd());
+ ASSERT_SOME(chroot);
+
+ // Add /usr/bin/stat into the chroot.
+ const string usrbin = path::join(chroot.get()->rootfs, "usr", "bin");
+ ASSERT_SOME(os::mkdir(usrbin));
+ ASSERT_EQ(0, os::system("cp /usr/bin/stat " + path::join(usrbin, "stat")));
+
+ Clock::pause();
+
+ Try<Subprocess> s = chroot.get()->run(
+ "/usr/bin/stat -c %i / >" + path::join("/", "stat.output"));
+
+ CHECK_SOME(s);
+
+ // Advance time until the internal reaper reaps the subprocess.
+ while (s.get().status().isPending()) {
+ Clock::advance(Seconds(1));
+ Clock::settle();
+ }
+
+ AWAIT_ASSERT_READY(s.get().status());
+ ASSERT_SOME(s.get().status().get());
+
+ int status = s.get().status().get().get();
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(0, WEXITSTATUS(status));
+
+ // Check the chroot has a different root by comparing the inodes.
+ Try<ino_t> self = os::stat::inode("/");
+ ASSERT_SOME(self);
+
+ Try<string> read = os::read(path::join(chroot.get()->rootfs, "stat.output"));
+ CHECK_SOME(read);
+
+ Try<ino_t> other = numify<ino_t>(strings::trim(read.get()));
+ ASSERT_SOME(other);
+
+ EXPECT_NE(self.get(), other.get());
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/launcher.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/launcher.hpp b/src/tests/containerizer/launcher.hpp
new file mode 100644
index 0000000..78216e0
--- /dev/null
+++ b/src/tests/containerizer/launcher.hpp
@@ -0,0 +1,119 @@
+/**
+ * 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 <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include <mesos/mesos.hpp>
+
+#include <mesos/slave/isolator.hpp>
+
+#include <process/future.hpp>
+#include <process/owned.hpp>
+#include <process/subprocess.hpp>
+
+#include <stout/flags.hpp>
+#include <stout/lambda.hpp>
+#include <stout/nothing.hpp>
+#include <stout/option.hpp>
+
+#include "slave/containerizer/launcher.hpp"
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+ACTION_P(InvokeRecover, launcher)
+{
+ return launcher->real->recover(arg0);
+}
+
+
+ACTION_P(InvokeFork, launcher)
+{
+ return launcher->real->fork(
+ arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+}
+
+
+ACTION_P(InvokeDestroy, launcher)
+{
+ return launcher->real->destroy(arg0);
+}
+
+
+class TestLauncher : public slave::Launcher
+{
+public:
+ TestLauncher(const process::Owned<slave::Launcher>& _real)
+ : real(_real)
+ {
+ using testing::_;
+ using testing::DoDefault;
+
+ ON_CALL(*this, recover(_))
+ .WillByDefault(InvokeRecover(this));
+ EXPECT_CALL(*this, recover(_))
+ .WillRepeatedly(DoDefault());
+
+ ON_CALL(*this, fork(_, _, _, _, _, _, _, _, _))
+ .WillByDefault(InvokeFork(this));
+ EXPECT_CALL(*this, fork(_, _, _, _, _, _, _, _, _))
+ .WillRepeatedly(DoDefault());
+
+ ON_CALL(*this, destroy(_))
+ .WillByDefault(InvokeDestroy(this));
+ EXPECT_CALL(*this, destroy(_))
+ .WillRepeatedly(DoDefault());
+ }
+
+ ~TestLauncher() {}
+
+ MOCK_METHOD1(
+ recover,
+ process::Future<hashset<ContainerID>>(
+ const std::list<mesos::slave::ExecutorRunState>& states));
+
+ MOCK_METHOD9(
+ fork,
+ Try<pid_t>(
+ const ContainerID& containerId,
+ const std::string& path,
+ const std::vector<std::string>& argv,
+ const process::Subprocess::IO& in,
+ const process::Subprocess::IO& out,
+ const process::Subprocess::IO& err,
+ const Option<flags::FlagsBase>& flags,
+ const Option<std::map<std::string, std::string> >& env,
+ const Option<lambda::function<int()> >& setup));
+
+ MOCK_METHOD1(
+ destroy,
+ process::Future<Nothing>(const ContainerID& containerId));
+
+ process::Owned<slave::Launcher> real;
+};
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/memory_pressure_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/memory_pressure_tests.cpp b/src/tests/containerizer/memory_pressure_tests.cpp
new file mode 100644
index 0000000..8089879
--- /dev/null
+++ b/src/tests/containerizer/memory_pressure_tests.cpp
@@ -0,0 +1,293 @@
+/**
+ * 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 <vector>
+
+#include <mesos/resources.hpp>
+#include <mesos/scheduler.hpp>
+
+#include <process/gtest.hpp>
+
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+
+#include "master/master.hpp"
+
+#include "slave/slave.hpp"
+
+#include "slave/containerizer/containerizer.hpp"
+#include "slave/containerizer/fetcher.hpp"
+
+#include "messages/messages.hpp"
+
+#include "tests/mesos.hpp"
+
+using namespace process;
+
+using mesos::internal::master::Master;
+
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::MesosContainerizer;
+using mesos::internal::slave::Slave;
+
+using std::vector;
+
+using testing::_;
+using testing::Eq;
+using testing::Return;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class MemoryPressureMesosTest : public ContainerizerTest<MesosContainerizer>
+{
+public:
+ static void SetUpTestCase()
+ {
+ // Verify that the dd command and its flags used in a bit are valid
+ // on this system.
+ ASSERT_EQ(0, os::system("dd count=1 bs=1M if=/dev/zero of=/dev/null"))
+ << "Cannot find a compatible 'dd' command";
+ }
+};
+
+
+TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_Statistics)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ // We only care about memory cgroup for this test.
+ flags.isolation = "cgroups/mem";
+ flags.slave_subsystems = None();
+
+ Fetcher fetcher;
+
+ 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(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ Offer offer = offers.get()[0];
+
+ // Run a task that triggers memory pressure event. We request 1G
+ // disk because we are going to write a 512 MB file repeatedly.
+ TaskInfo task = createTask(
+ offer.slave_id(),
+ Resources::parse("cpus:1;mem:256;disk:1024").get(),
+ "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done");
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status))
+ .WillRepeatedly(Return()); // Ignore subsequent updates.
+
+ driver.launchTasks(offer.id(), {task});
+
+ AWAIT_READY(status);
+ EXPECT_EQ(task.task_id(), status.get().task_id());
+ EXPECT_EQ(TASK_RUNNING, status.get().state());
+
+ Future<hashset<ContainerID>> containers = containerizer.get()->containers();
+ AWAIT_READY(containers);
+ ASSERT_EQ(1u, containers.get().size());
+
+ ContainerID containerId = *(containers.get().begin());
+
+ Duration waited = Duration::zero();
+ do {
+ Future<ResourceStatistics> usage = containerizer.get()->usage(containerId);
+ AWAIT_READY(usage);
+
+ if (usage.get().mem_low_pressure_counter() > 0) {
+ EXPECT_GE(usage.get().mem_low_pressure_counter(),
+ usage.get().mem_medium_pressure_counter());
+ EXPECT_GE(usage.get().mem_medium_pressure_counter(),
+ usage.get().mem_critical_pressure_counter());
+ break;
+ }
+
+ os::sleep(Milliseconds(100));
+ waited += Milliseconds(100);
+ } while (waited < Seconds(5));
+
+ EXPECT_LE(waited, Seconds(5));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+ delete containerizer.get();
+}
+
+
+// Test that memory pressure listening is restarted after recovery.
+TEST_F(MemoryPressureMesosTest, CGROUPS_ROOT_SlaveRecovery)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ // We only care about memory cgroup for this test.
+ flags.isolation = "cgroups/mem";
+ flags.slave_subsystems = None();
+
+ Fetcher fetcher;
+
+ Try<MesosContainerizer*> containerizer1 =
+ MesosContainerizer::create(flags, true, &fetcher);
+
+ ASSERT_SOME(containerizer1);
+
+ Try<PID<Slave>> slave = StartSlave(containerizer1.get(), 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);
+
+ EXPECT_CALL(sched, registered(_, _, _));
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(_, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(offers);
+ EXPECT_NE(0u, offers.get().size());
+
+ Offer offer = offers.get()[0];
+
+ // Run a task that triggers memory pressure event. We request 1G
+ // disk because we are going to write a 512 MB file repeatedly.
+ TaskInfo task = createTask(
+ offer.slave_id(),
+ Resources::parse("cpus:1;mem:256;disk:1024").get(),
+ "while true; do dd count=512 bs=1M if=/dev/zero of=./temp; done");
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&status))
+ .WillRepeatedly(Return()); // Ignore subsequent updates.
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(status);
+ EXPECT_EQ(task.task_id(), status.get().task_id());
+ EXPECT_EQ(TASK_RUNNING, status.get().state());
+
+ // We restart the slave to let it recover.
+ Stop(slave.get());
+ delete containerizer1.get();
+
+ Future<Nothing> _recover = FUTURE_DISPATCH(_, &Slave::_recover);
+
+ Future<SlaveReregisteredMessage> slaveReregisteredMessage =
+ FUTURE_PROTOBUF(SlaveReregisteredMessage(), _, _);
+
+ // Use the same flags.
+ Try<MesosContainerizer*> containerizer2 =
+ MesosContainerizer::create(flags, true, &fetcher);
+
+ ASSERT_SOME(containerizer2);
+
+ slave = StartSlave(containerizer2.get(), flags);
+ ASSERT_SOME(slave);
+
+ Clock::pause();
+
+ AWAIT_READY(_recover);
+
+ // Wait for slave to schedule reregister timeout.
+ Clock::settle();
+
+ // Ensure the slave considers itself recovered.
+ Clock::advance(slave::EXECUTOR_REREGISTER_TIMEOUT);
+
+ Clock::resume();
+
+ // Wait for the slave to re-register.
+ AWAIT_READY(slaveReregisteredMessage);
+
+ Future<hashset<ContainerID>> containers = containerizer2.get()->containers();
+ AWAIT_READY(containers);
+ ASSERT_EQ(1u, containers.get().size());
+
+ ContainerID containerId = *(containers.get().begin());
+
+ Duration waited = Duration::zero();
+ do {
+ Future<ResourceStatistics> usage = containerizer2.get()->usage(containerId);
+ AWAIT_READY(usage);
+
+ if (usage.get().mem_low_pressure_counter() > 0) {
+ EXPECT_GE(usage.get().mem_low_pressure_counter(),
+ usage.get().mem_medium_pressure_counter());
+ EXPECT_GE(usage.get().mem_medium_pressure_counter(),
+ usage.get().mem_critical_pressure_counter());
+ break;
+ }
+
+ os::sleep(Milliseconds(100));
+ waited += Milliseconds(100);
+ } while (waited < Seconds(5));
+
+ EXPECT_LE(waited, Seconds(5));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+ delete containerizer2.get();
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
[10/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/docker_containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/docker_containerizer_tests.cpp b/src/tests/containerizer/docker_containerizer_tests.cpp
new file mode 100644
index 0000000..80ed60e
--- /dev/null
+++ b/src/tests/containerizer/docker_containerizer_tests.cpp
@@ -0,0 +1,2955 @@
+/**
+ * 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 <gtest/gtest.h>
+
+#include <process/future.hpp>
+#include <process/gmock.hpp>
+#include <process/owned.hpp>
+#include <process/subprocess.hpp>
+
+#include <stout/duration.hpp>
+
+#include "linux/cgroups.hpp"
+
+#include "messages/messages.hpp"
+
+#include "tests/flags.hpp"
+#include "tests/mesos.hpp"
+
+#include "slave/containerizer/docker.hpp"
+#include "slave/containerizer/fetcher.hpp"
+
+#include "slave/paths.hpp"
+#include "slave/slave.hpp"
+#include "slave/state.hpp"
+
+using namespace mesos::internal::slave::paths;
+using namespace mesos::internal::slave::state;
+
+using namespace process;
+
+using mesos::internal::master::Master;
+
+using mesos::internal::slave::DockerContainerizer;
+using mesos::internal::slave::DockerContainerizerProcess;
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::Slave;
+
+using process::Future;
+using process::Message;
+using process::PID;
+using process::UPID;
+
+using std::vector;
+using std::list;
+using std::string;
+
+using testing::_;
+using testing::DoAll;
+using testing::DoDefault;
+using testing::Eq;
+using testing::Invoke;
+using testing::Return;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+class MockDocker : public Docker
+{
+public:
+ MockDocker(const string& path) : Docker(path)
+ {
+ EXPECT_CALL(*this, pull(_, _, _))
+ .WillRepeatedly(Invoke(this, &MockDocker::_pull));
+
+ EXPECT_CALL(*this, stop(_, _, _))
+ .WillRepeatedly(Invoke(this, &MockDocker::_stop));
+
+ EXPECT_CALL(*this, run(_, _, _, _, _, _, _, _, _))
+ .WillRepeatedly(Invoke(this, &MockDocker::_run));
+
+ EXPECT_CALL(*this, inspect(_, _))
+ .WillRepeatedly(Invoke(this, &MockDocker::_inspect));
+ }
+
+ MOCK_CONST_METHOD9(
+ run,
+ process::Future<Nothing>(
+ const mesos::ContainerInfo&,
+ const mesos::CommandInfo&,
+ const std::string&,
+ const std::string&,
+ const std::string&,
+ const Option<mesos::Resources>&,
+ const Option<std::map<std::string, std::string>>&,
+ const Option<std::string>&,
+ const Option<std::string>&));
+
+ MOCK_CONST_METHOD3(
+ pull,
+ process::Future<Docker::Image>(
+ const string&,
+ const string&,
+ bool));
+
+ MOCK_CONST_METHOD3(
+ stop,
+ process::Future<Nothing>(
+ const string&,
+ const Duration&,
+ bool));
+
+ MOCK_CONST_METHOD2(
+ inspect,
+ process::Future<Docker::Container>(
+ const string&,
+ const Option<Duration>&));
+
+ process::Future<Nothing> _run(
+ const mesos::ContainerInfo& containerInfo,
+ const mesos::CommandInfo& commandInfo,
+ const std::string& name,
+ const std::string& sandboxDirectory,
+ const std::string& mappedDirectory,
+ const Option<mesos::Resources>& resources,
+ const Option<std::map<std::string, std::string>>& env,
+ const Option<std::string>& stdoutPath,
+ const Option<std::string>& stderrPath) const
+ {
+ return Docker::run(
+ containerInfo,
+ commandInfo,
+ name,
+ sandboxDirectory,
+ mappedDirectory,
+ resources,
+ env,
+ stdoutPath,
+ stderrPath);
+ }
+
+ process::Future<Docker::Image> _pull(
+ const string& directory,
+ const string& image,
+ bool force) const
+ {
+ return Docker::pull(directory, image, force);
+ }
+
+ process::Future<Nothing> _stop(
+ const string& containerName,
+ const Duration& timeout,
+ bool remove) const
+ {
+ return Docker::stop(containerName, timeout, remove);
+ }
+
+ process::Future<Docker::Container> _inspect(
+ const string& containerName,
+ const Option<Duration>& retryInterval)
+ {
+ return Docker::inspect(containerName, retryInterval);
+ }
+};
+
+
+class DockerContainerizerTest : public MesosTest
+{
+public:
+ static string containerName(
+ const SlaveID& slaveId,
+ const ContainerID& containerId)
+ {
+ return slave::DOCKER_NAME_PREFIX + slaveId.value() +
+ slave::DOCKER_NAME_SEPERATOR + containerId.value();
+ }
+
+ enum ContainerState {
+ EXISTS,
+ RUNNING
+ };
+
+ static bool exists(
+ const process::Shared<Docker>& docker,
+ const SlaveID& slaveId,
+ const ContainerID& containerId,
+ ContainerState state = ContainerState::EXISTS)
+ {
+ Duration waited = Duration::zero();
+ string expectedName = containerName(slaveId, containerId);
+
+ do {
+ Future<Docker::Container> inspect = docker->inspect(expectedName);
+
+ if (!inspect.await(Seconds(3))) {
+ return false;
+ }
+
+ if (inspect.isReady()) {
+ switch (state) {
+ case ContainerState::RUNNING:
+ if (inspect.get().pid.isSome()) {
+ return true;
+ }
+ // Retry looking for running pid until timeout.
+ break;
+ case ContainerState::EXISTS:
+ return true;
+ }
+ }
+
+ os::sleep(Milliseconds(200));
+ waited += Milliseconds(200);
+ } while (waited < Seconds(5));
+
+ return false;
+ }
+
+ static bool containsLine(
+ const vector<string>& lines,
+ const string& expectedLine)
+ {
+ foreach (const string& line, lines) {
+ if (line == expectedLine) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ virtual void TearDown()
+ {
+ Try<Docker*> docker = Docker::create(tests::flags.docker, false);
+ ASSERT_SOME(docker);
+ Future<list<Docker::Container>> containers =
+ docker.get()->ps(true, slave::DOCKER_NAME_PREFIX);
+
+ AWAIT_READY(containers);
+
+ // Cleanup all mesos launched containers.
+ foreach (const Docker::Container& container, containers.get()) {
+ AWAIT_READY_FOR(docker.get()->rm(container.id, true), Seconds(30));
+ }
+
+ delete docker.get();
+ }
+};
+
+
+class MockDockerContainerizer : public DockerContainerizer {
+public:
+ MockDockerContainerizer(
+ const slave::Flags& flags,
+ Fetcher* fetcher,
+ Shared<Docker> docker)
+ : DockerContainerizer(flags, fetcher, docker)
+ {
+ initialize();
+ }
+
+ MockDockerContainerizer(const Owned<DockerContainerizerProcess>& process)
+ : DockerContainerizer(process)
+ {
+ initialize();
+ }
+
+ void initialize()
+ {
+ // NOTE: See TestContainerizer::setup for why we use
+ // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
+ // 'ON_CALL' and 'WillByDefault'.
+ EXPECT_CALL(*this, launch(_, _, _, _, _, _, _))
+ .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_launchExecutor));
+
+ EXPECT_CALL(*this, launch(_, _, _, _, _, _, _, _))
+ .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_launch));
+
+ EXPECT_CALL(*this, update(_, _))
+ .WillRepeatedly(Invoke(this, &MockDockerContainerizer::_update));
+ }
+
+ MOCK_METHOD7(
+ launch,
+ process::Future<bool>(
+ const ContainerID&,
+ const ExecutorInfo&,
+ const std::string&,
+ const Option<std::string>&,
+ const SlaveID&,
+ const process::PID<slave::Slave>&,
+ bool checkpoint));
+
+ MOCK_METHOD8(
+ launch,
+ process::Future<bool>(
+ const ContainerID&,
+ const TaskInfo&,
+ const ExecutorInfo&,
+ const std::string&,
+ const Option<std::string>&,
+ const SlaveID&,
+ const process::PID<slave::Slave>&,
+ bool checkpoint));
+
+ MOCK_METHOD2(
+ update,
+ process::Future<Nothing>(
+ const ContainerID&,
+ const Resources&));
+
+ // Default 'launch' implementation (necessary because we can't just
+ // use &DockerContainerizer::launch with 'Invoke').
+ process::Future<bool> _launch(
+ const ContainerID& containerId,
+ const TaskInfo& taskInfo,
+ const ExecutorInfo& executorInfo,
+ const string& directory,
+ const Option<string>& user,
+ const SlaveID& slaveId,
+ const PID<Slave>& slavePid,
+ bool checkpoint)
+ {
+ return DockerContainerizer::launch(
+ containerId,
+ taskInfo,
+ executorInfo,
+ directory,
+ user,
+ slaveId,
+ slavePid,
+ checkpoint);
+ }
+
+ process::Future<bool> _launchExecutor(
+ const ContainerID& containerId,
+ const ExecutorInfo& executorInfo,
+ const string& directory,
+ const Option<string>& user,
+ const SlaveID& slaveId,
+ const PID<Slave>& slavePid,
+ bool checkpoint)
+ {
+ return DockerContainerizer::launch(
+ containerId,
+ executorInfo,
+ directory,
+ user,
+ slaveId,
+ slavePid,
+ checkpoint);
+ }
+
+ process::Future<Nothing> _update(
+ const ContainerID& containerId,
+ const Resources& resources)
+ {
+ return DockerContainerizer::update(
+ containerId,
+ resources);
+ }
+};
+
+
+class MockDockerContainerizerProcess : public DockerContainerizerProcess
+{
+public:
+ MockDockerContainerizerProcess(
+ const slave::Flags& flags,
+ Fetcher* fetcher,
+ const Shared<Docker>& docker)
+ : DockerContainerizerProcess(flags, fetcher, docker)
+ {
+ EXPECT_CALL(*this, fetch(_, _))
+ .WillRepeatedly(Invoke(this, &MockDockerContainerizerProcess::_fetch));
+
+ EXPECT_CALL(*this, pull(_))
+ .WillRepeatedly(Invoke(this, &MockDockerContainerizerProcess::_pull));
+ }
+
+ MOCK_METHOD2(
+ fetch,
+ process::Future<Nothing>(
+ const ContainerID& containerId,
+ const SlaveID& slaveId));
+
+ MOCK_METHOD1(
+ pull,
+ process::Future<Nothing>(const ContainerID& containerId));
+
+ process::Future<Nothing> _fetch(
+ const ContainerID& containerId,
+ const SlaveID& slaveId)
+ {
+ return DockerContainerizerProcess::fetch(containerId, slaveId);
+ }
+
+ process::Future<Nothing> _pull(const ContainerID& containerId)
+ {
+ return DockerContainerizerProcess::pull(containerId);
+ }
+};
+
+
+// Only enable executor launch on linux as other platforms
+// requires running linux VM and need special port forwarding
+// to get host networking to work.
+#ifdef __linux__
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch_Executor)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ ExecutorInfo executorInfo;
+ ExecutorID executorId;
+ executorId.set_value("e1");
+ executorInfo.mutable_executor_id()->CopyFrom(executorId);
+
+ CommandInfo command;
+ command.set_value("/bin/test-executor");
+ executorInfo.mutable_command()->CopyFrom(command);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("tnachen/test-executor");
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ task.mutable_executor()->CopyFrom(executorInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launchExecutor)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ ASSERT_FALSE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ Shutdown();
+}
+
+
+// This test verifies that a custom executor can be launched and
+// registered with the slave with docker bridge network enabled.
+// We're assuming that the custom executor is registering it's public
+// ip instead of 0.0.0.0 or equivelent to the slave as that's the
+// default behavior for libprocess.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch_Executor_Bridged)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ ExecutorInfo executorInfo;
+ ExecutorID executorId;
+ executorId.set_value("e1");
+ executorInfo.mutable_executor_id()->CopyFrom(executorId);
+
+ CommandInfo command;
+ command.set_value("/bin/test-executor");
+ executorInfo.mutable_command()->CopyFrom(command);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("tnachen/test-executor");
+ dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ task.mutable_executor()->CopyFrom(executorInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launchExecutor)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ ASSERT_FALSE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ Shutdown();
+}
+#endif // __linux__
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Launch)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ ASSERT_TRUE(statusRunning.get().has_data());
+
+ Try<JSON::Array> parse = JSON::parse<JSON::Array>(statusRunning.get().data());
+ ASSERT_SOME(parse);
+
+ // Now verify that the Docker.NetworkSettings.IPAddress label is
+ // present.
+ ASSERT_TRUE(statusRunning.get().has_labels());
+ EXPECT_EQ(1, statusRunning.get().labels().labels().size());
+ EXPECT_EQ("Docker.NetworkSettings.IPAddress",
+ statusRunning.get().labels().labels(0).key());
+
+ ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ ASSERT_FALSE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ Shutdown();
+}
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Kill)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+
+ ASSERT_TRUE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ Future<TaskStatus> statusKilled;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusKilled));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.killTask(task.task_id());
+
+ AWAIT_READY(statusKilled);
+ EXPECT_EQ(TASK_KILLED, statusKilled.get().state());
+
+ AWAIT_READY(termination);
+
+ ASSERT_FALSE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// This test tests DockerContainerizer::usage().
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Usage)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+ flags.resources = Option<string>("cpus:2;mem:1024");
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ // Run a CPU intensive command, so we can measure utime and stime later.
+ command.set_value("dd if=/dev/zero of=/dev/null");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ // We ignore all update calls to prevent resizing cgroup limits.
+ EXPECT_CALL(dockerContainerizer, update(_, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Future<TaskStatus> statusRunning;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+
+ // Verify the usage.
+ ResourceStatistics statistics;
+ Duration waited = Duration::zero();
+ do {
+ Future<ResourceStatistics> usage =
+ dockerContainerizer.usage(containerId.get());
+ // TODO(tnachen): Replace await with AWAIT_COMPLETED once
+ // implemented.
+ ASSERT_TRUE(usage.await(Seconds(3)));
+
+ if (usage.isReady()) {
+ statistics = usage.get();
+
+ if (statistics.cpus_user_time_secs() > 0 &&
+ statistics.cpus_system_time_secs() > 0) {
+ break;
+ }
+ }
+
+ os::sleep(Milliseconds(200));
+ waited += Milliseconds(200);
+ } while (waited < Seconds(3));
+
+ // Usage includes the executor resources.
+ EXPECT_EQ(2.0 + slave::DEFAULT_EXECUTOR_CPUS, statistics.cpus_limit());
+ EXPECT_EQ((Gigabytes(1) + slave::DEFAULT_EXECUTOR_MEM).bytes(),
+ statistics.mem_limit_bytes());
+ EXPECT_LT(0, statistics.cpus_user_time_secs());
+ EXPECT_LT(0, statistics.cpus_system_time_secs());
+ EXPECT_GT(statistics.mem_rss_bytes(), 0u);
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ dockerContainerizer.destroy(containerId.get());
+
+ AWAIT_READY(termination);
+
+ // Usage() should fail again since the container is destroyed.
+ Future<ResourceStatistics> usage =
+ dockerContainerizer.usage(containerId.get());
+
+ AWAIT_FAILED(usage);
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+#ifdef __linux__
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Update)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(containerId);
+
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+
+ ASSERT_TRUE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ string name = containerName(slaveId, containerId.get());
+
+ Future<Docker::Container> inspect = docker->inspect(name);
+
+ AWAIT_READY(inspect);
+
+ Try<Resources> newResources = Resources::parse("cpus:1;mem:128");
+
+ ASSERT_SOME(newResources);
+
+ Future<Nothing> update =
+ dockerContainerizer.update(containerId.get(), newResources.get());
+
+ AWAIT_READY(update);
+
+ Result<string> cpuHierarchy = cgroups::hierarchy("cpu");
+ Result<string> memoryHierarchy = cgroups::hierarchy("memory");
+
+ ASSERT_SOME(cpuHierarchy);
+ ASSERT_SOME(memoryHierarchy);
+
+ Option<pid_t> pid = inspect.get().pid;
+ ASSERT_SOME(pid);
+
+ Result<string> cpuCgroup = cgroups::cpu::cgroup(pid.get());
+ ASSERT_SOME(cpuCgroup);
+
+ Result<string> memoryCgroup = cgroups::memory::cgroup(pid.get());
+ ASSERT_SOME(memoryCgroup);
+
+ Try<uint64_t> cpu = cgroups::cpu::shares(
+ cpuHierarchy.get(),
+ cpuCgroup.get());
+
+ ASSERT_SOME(cpu);
+
+ Try<Bytes> mem = cgroups::memory::soft_limit_in_bytes(
+ memoryHierarchy.get(),
+ memoryCgroup.get());
+
+ ASSERT_SOME(mem);
+
+ EXPECT_EQ(1024u, cpu.get());
+ EXPECT_EQ(128u, mem.get().megabytes());
+
+ newResources = Resources::parse("cpus:1;mem:144");
+
+ // Issue second update that uses the cached pid instead of inspect.
+ update = dockerContainerizer.update(containerId.get(), newResources.get());
+
+ AWAIT_READY(update);
+
+ cpu = cgroups::cpu::shares(cpuHierarchy.get(), cpuCgroup.get());
+
+ ASSERT_SOME(cpu);
+
+ mem = cgroups::memory::soft_limit_in_bytes(
+ memoryHierarchy.get(),
+ memoryCgroup.get());
+
+ ASSERT_SOME(mem);
+
+ EXPECT_EQ(1024u, cpu.get());
+ EXPECT_EQ(144u, mem.get().megabytes());
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+#endif //__linux__
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Recover)
+{
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Future<string> stoppedContainer;
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&stoppedContainer),
+ Return(Nothing())));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ SlaveID slaveId;
+ slaveId.set_value("s1");
+ ContainerID containerId;
+ containerId.set_value("c1");
+ ContainerID reapedContainerId;
+ reapedContainerId.set_value("c2");
+
+ string container1 = containerName(slaveId, containerId);
+ string container2 = containerName(slaveId, reapedContainerId);
+
+ // Clean up artifacts if containers still exists.
+ ASSERT_TRUE(docker->rm(container1, true).await(Seconds(30)));
+ ASSERT_TRUE(docker->rm(container2, true).await(Seconds(30)));
+
+ Resources resources = Resources::parse("cpus:1;mem:512").get();
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ CommandInfo commandInfo;
+ commandInfo.set_value("sleep 1000");
+
+ Future<Nothing> d1 =
+ docker->run(
+ containerInfo,
+ commandInfo,
+ container1,
+ flags.work_dir,
+ flags.sandbox_directory,
+ resources);
+
+ Future<Nothing> d2 =
+ docker->run(
+ containerInfo,
+ commandInfo,
+ container2,
+ flags.work_dir,
+ flags.sandbox_directory,
+ resources);
+
+ ASSERT_TRUE(
+ exists(docker, slaveId, containerId, ContainerState::RUNNING));
+ ASSERT_TRUE(
+ exists(docker, slaveId, reapedContainerId, ContainerState::RUNNING));
+
+ Future<Docker::Container> inspect = docker->inspect(container2);
+ AWAIT_READY(inspect);
+
+ SlaveState slaveState;
+ slaveState.id = slaveId;
+ FrameworkState frameworkState;
+
+ ExecutorID execId;
+ execId.set_value("e1");
+
+ ExecutorState execState;
+ ExecutorInfo execInfo;
+ execState.info = execInfo;
+ execState.latest = containerId;
+
+ Try<process::Subprocess> wait =
+ process::subprocess(tests::flags.docker + " wait " + container1);
+
+ ASSERT_SOME(wait);
+
+ FrameworkID frameworkId;
+
+ RunState runState;
+ runState.id = containerId;
+ runState.forkedPid = wait.get().pid();
+ execState.runs.put(containerId, runState);
+ frameworkState.executors.put(execId, execState);
+
+ slaveState.frameworks.put(frameworkId, frameworkState);
+
+ Future<Nothing> recover = dockerContainerizer.recover(slaveState);
+
+ AWAIT_READY(recover);
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId);
+
+ ASSERT_FALSE(termination.isFailed());
+
+ AWAIT_FAILED(dockerContainerizer.wait(reapedContainerId));
+
+ AWAIT_EQ(inspect.get().id, stoppedContainer);
+
+ Shutdown();
+}
+
+
+// This test checks the docker containerizer doesn't recover executors
+// that were started by another containerizer (e.g: mesos).
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_SkipRecoverNonDocker)
+{
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ ContainerID containerId;
+ containerId.set_value("c1");
+ ContainerID reapedContainerId;
+ reapedContainerId.set_value("c2");
+
+ ExecutorID executorId;
+ executorId.set_value(UUID::random().toString());
+
+ ExecutorInfo executorInfo;
+ executorInfo.mutable_container()->set_type(ContainerInfo::MESOS);
+
+ ExecutorState executorState;
+ executorState.info = executorInfo;
+ executorState.latest = containerId;
+
+ RunState runState;
+ runState.id = containerId;
+ executorState.runs.put(containerId, runState);
+
+ FrameworkState frameworkState;
+ frameworkState.executors.put(executorId, executorState);
+
+ SlaveState slaveState;
+ FrameworkID frameworkId;
+ frameworkId.set_value(UUID::random().toString());
+ slaveState.frameworks.put(frameworkId, frameworkState);
+
+ Future<Nothing> recover = dockerContainerizer.recover(slaveState);
+ AWAIT_READY(recover);
+
+ Future<hashset<ContainerID>> containers = dockerContainerizer.containers();
+ AWAIT_READY(containers);
+
+ // A MesosContainerizer task shouldn't be recovered by
+ // DockerContainerizer.
+ EXPECT_EQ(0u, containers.get().size());
+}
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Logs)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ // We skip stopping the docker container because stopping a container
+ // even when it terminated might not flush the logs and we end up
+ // not getting stdout/stderr in our tests.
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ string uuid = UUID::random().toString();
+
+ CommandInfo command;
+ command.set_value("echo out" + uuid + " ; echo err" + uuid + " 1>&2");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ Future<string> directory;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<3>(&directory),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY(directory);
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ // Now check that the proper output is in stderr and stdout (which
+ // might also contain other things, hence the use of a UUID).
+ Try<string> read = os::read(path::join(directory.get(), "stderr"));
+ ASSERT_SOME(read);
+
+ vector<string> lines = strings::split(read.get(), "\n");
+
+ EXPECT_TRUE(containsLine(lines, "err" + uuid));
+ EXPECT_FALSE(containsLine(lines, "out" + uuid));
+
+ read = os::read(path::join(directory.get(), "stdout"));
+ ASSERT_SOME(read);
+
+ lines = strings::split(read.get(), "\n");
+
+ EXPECT_TRUE(containsLine(lines, "out" + uuid));
+ EXPECT_FALSE(containsLine(lines, "err" + uuid));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// The following test uses a Docker image (mesosphere/inky) that has
+// an entrypoint "echo" and a default command "inky".
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ // We skip stopping the docker container because stopping a container
+ // even when it terminated might not flush the logs and we end up
+ // not getting stdout/stderr in our tests.
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_shell(false);
+
+ // NOTE: By not setting CommandInfo::value we're testing that we
+ // will still be able to run the container because it has a default
+ // entrypoint!
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("mesosphere/inky");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ Future<string> directory;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<3>(&directory),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY(directory);
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ Try<string> read = os::read(path::join(directory.get(), "stdout"));
+ ASSERT_SOME(read);
+
+ vector<string> lines = strings::split(read.get(), "\n");
+
+ // Since we're not passing any command value, we're expecting the
+ // default entry point to be run which is 'echo' with the default
+ // command from the image which is 'inky'.
+ EXPECT_TRUE(containsLine(lines, "inky"));
+
+ read = os::read(path::join(directory.get(), "stderr"));
+ ASSERT_SOME(read);
+
+ lines = strings::split(read.get(), "\n");
+
+ EXPECT_FALSE(containsLine(lines, "inky"));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// The following test uses a Docker image (mesosphere/inky) that has
+// an entrypoint "echo" and a default command "inky".
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD_Override)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ // We skip stopping the docker container because stopping a container
+ // even when it terminated might not flush the logs and we end up
+ // not getting stdout/stderr in our tests.
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ string uuid = UUID::random().toString();
+
+ CommandInfo command;
+ command.set_shell(false);
+
+ // We can set the value to just the 'uuid' since it should get
+ // passed as an argument to the entrypoint, i.e., 'echo uuid'.
+ command.set_value(uuid);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("mesosphere/inky");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ Future<string> directory;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<3>(&directory),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY(directory);
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ // Now check that the proper output is in stderr and stdout.
+ Try<string> read = os::read(path::join(directory.get(), "stdout"));
+ ASSERT_SOME(read);
+
+ vector<string> lines = strings::split(read.get(), "\n");
+
+ // We expect the passed in command value to override the image's
+ // default command, thus we should see the value of 'uuid' in the
+ // output instead of the default command which is 'inky'.
+ EXPECT_TRUE(containsLine(lines, uuid));
+ EXPECT_FALSE(containsLine(lines, "inky"));
+
+ read = os::read(path::join(directory.get(), "stderr"));
+ ASSERT_SOME(read);
+
+ lines = strings::split(read.get(), "\n");
+
+ EXPECT_FALSE(containsLine(lines, "inky"));
+ EXPECT_FALSE(containsLine(lines, uuid));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// The following test uses a Docker image (mesosphere/inky) that has
+// an entrypoint "echo" and a default command "inky".
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_Default_CMD_Args)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ // We skip stopping the docker container because stopping a container
+ // even when it terminated might not flush the logs and we end up
+ // not getting stdout/stderr in our tests.
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ string uuid = UUID::random().toString();
+
+ CommandInfo command;
+ command.set_shell(false);
+
+ // We should also be able to skip setting the comamnd value and just
+ // set the arguments and those should also get passed through to the
+ // entrypoint!
+ command.add_arguments(uuid);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("mesosphere/inky");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ Future<string> directory;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<3>(&directory),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY(directory);
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ // Now check that the proper output is in stderr and stdout.
+ Try<string> read = os::read(path::join(directory.get(), "stdout"));
+ ASSERT_SOME(read);
+
+ vector<string> lines = strings::split(read.get(), "\n");
+
+ // We expect the passed in command arguments to override the image's
+ // default command, thus we should see the value of 'uuid' in the
+ // output instead of the default command which is 'inky'.
+ EXPECT_TRUE(containsLine(lines, uuid));
+ EXPECT_FALSE(containsLine(lines, "inky"));
+
+ read = os::read(path::join(directory.get(), "stderr"));
+ ASSERT_SOME(read);
+
+ lines = strings::split(read.get(), "\n");
+
+ EXPECT_FALSE(containsLine(lines, "inky"));
+ EXPECT_FALSE(containsLine(lines, uuid));
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// The slave is stopped before the first update for a task is received
+// from the executor. When it comes back up we make sure the executor
+// re-registers and the slave properly sends the update.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_SlaveRecoveryTaskContainer)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // We put the containerizer on the heap so we can more easily
+ // control it's lifetime, i.e., when we invoke the destructor.
+ MockDockerContainerizer* dockerContainerizer1 =
+ new MockDockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave1 = StartSlave(dockerContainerizer1, flags);
+ ASSERT_SOME(slave1);
+
+ // Enable checkpointing for the framework.
+ FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+ frameworkInfo.set_checkpoint(true);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(*dockerContainerizer1, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(dockerContainerizer1,
+ &MockDockerContainerizer::_launch)));
+
+ // Drop the first update from the executor.
+ Future<StatusUpdateMessage> statusUpdateMessage =
+ DROP_PROTOBUF(StatusUpdateMessage(), _, _);
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(containerId);
+
+ // Stop the slave before the status update is received.
+ AWAIT_READY(statusUpdateMessage);
+
+ Stop(slave1.get());
+
+ delete dockerContainerizer1;
+
+ Future<Message> reregisterExecutorMessage =
+ FUTURE_MESSAGE(Eq(ReregisterExecutorMessage().GetTypeName()), _, _);
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(_, _))
+ .WillOnce(FutureArg<1>(&status))
+ .WillRepeatedly(Return()); // Ignore subsequent updates.
+
+ MockDockerContainerizer* dockerContainerizer2 =
+ new MockDockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave2 = StartSlave(dockerContainerizer2, flags);
+ ASSERT_SOME(slave2);
+
+ // Ensure the executor re-registers.
+ AWAIT_READY(reregisterExecutorMessage);
+ UPID executorPid = reregisterExecutorMessage.get().from;
+
+ ReregisterExecutorMessage reregister;
+ reregister.ParseFromString(reregisterExecutorMessage.get().body);
+
+ // Executor should inform about the unacknowledged update.
+ ASSERT_EQ(1, reregister.updates_size());
+ const StatusUpdate& update = reregister.updates(0);
+ ASSERT_EQ(task.task_id(), update.status().task_id());
+ ASSERT_EQ(TASK_RUNNING, update.status().state());
+
+ // Scheduler should receive the recovered update.
+ AWAIT_READY(status);
+ ASSERT_EQ(TASK_RUNNING, status.get().state());
+
+ ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer2->wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ Shutdown();
+
+ delete dockerContainerizer2;
+}
+
+
+// The slave is stopped before the first update for a task is received
+// from the executor. When it comes back up we make sure the executor
+// re-registers and the slave properly sends the update.
+//
+// TODO(benh): This test is currently disabled because the executor
+// inside the image mesosphere/test-executor does not properly set the
+// executor PID that is uses during registration, so when the new
+// slave recovers it can't reconnect and instead destroys that
+// container. In particular, it uses '0' for it's IP which we properly
+// parse and can even properly use for sending other messages, but the
+// current implementation of 'UPID::operator bool ()' fails if the IP
+// component of a PID is '0'.
+TEST_F(DockerContainerizerTest,
+ DISABLED_ROOT_DOCKER_SlaveRecoveryExecutorContainer)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer* dockerContainerizer1 =
+ new MockDockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave1 = StartSlave(dockerContainerizer1, flags);
+ ASSERT_SOME(slave1);
+
+ // Enable checkpointing for the framework.
+ FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+ frameworkInfo.set_checkpoint(true);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ ExecutorInfo executorInfo;
+ ExecutorID executorId;
+ executorId.set_value("e1");
+ executorInfo.mutable_executor_id()->CopyFrom(executorId);
+
+ CommandInfo command;
+ command.set_value("test-executor");
+ executorInfo.mutable_command()->CopyFrom(command);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("mesosphere/test-executor");
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ task.mutable_executor()->CopyFrom(executorInfo);
+
+ Future<ContainerID> containerId;
+ Future<SlaveID> slaveId;
+ EXPECT_CALL(*dockerContainerizer1, launch(_, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<4>(&slaveId),
+ Invoke(dockerContainerizer1,
+ &MockDockerContainerizer::_launchExecutor)));
+
+ // We need to wait until the container's pid has been been
+ // checkpointed so that when the next slave recovers it won't treat
+ // the executor as having gone lost! We know this has completed
+ // after Containerizer::launch returns and the
+ // Slave::executorLaunched gets dispatched.
+ Future<Nothing> executorLaunched =
+ FUTURE_DISPATCH(_, &Slave::executorLaunched);
+
+ // The test-executor in the image immediately sends a TASK_RUNNING
+ // followed by TASK_FINISHED (no sleep/delay in between) so we need
+ // to drop the first TWO updates that come from the executor rather
+ // than only the first update like above where we can control how
+ // the length of the task.
+ Future<StatusUpdateMessage> statusUpdateMessage1 =
+ DROP_PROTOBUF(StatusUpdateMessage(), _, _);
+
+ // Drop the first update from the executor.
+ Future<StatusUpdateMessage> statusUpdateMessage2 =
+ DROP_PROTOBUF(StatusUpdateMessage(), _, _);
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY(containerId);
+ AWAIT_READY(slaveId);
+
+ AWAIT_READY(executorLaunched);
+ AWAIT_READY(statusUpdateMessage1);
+ AWAIT_READY(statusUpdateMessage2);
+
+ Stop(slave1.get());
+
+ delete dockerContainerizer1;
+
+ Future<Message> reregisterExecutorMessage =
+ FUTURE_MESSAGE(Eq(ReregisterExecutorMessage().GetTypeName()), _, _);
+
+ Future<TaskStatus> status;
+ EXPECT_CALL(sched, statusUpdate(_, _))
+ .WillOnce(FutureArg<1>(&status))
+ .WillRepeatedly(Return()); // Ignore subsequent updates.
+
+ MockDockerContainerizer* dockerContainerizer2 =
+ new MockDockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave2 = StartSlave(dockerContainerizer2, flags);
+ ASSERT_SOME(slave2);
+
+ // Ensure the executor re-registers.
+ AWAIT_READY(reregisterExecutorMessage);
+ UPID executorPid = reregisterExecutorMessage.get().from;
+
+ ReregisterExecutorMessage reregister;
+ reregister.ParseFromString(reregisterExecutorMessage.get().body);
+
+ // Executor should inform about the unacknowledged update.
+ ASSERT_EQ(1, reregister.updates_size());
+ const StatusUpdate& update = reregister.updates(0);
+ ASSERT_EQ(task.task_id(), update.status().task_id());
+ ASSERT_EQ(TASK_RUNNING, update.status().state());
+
+ // Scheduler should receive the recovered update.
+ AWAIT_READY(status);
+ ASSERT_EQ(TASK_RUNNING, status.get().state());
+
+ ASSERT_TRUE(exists(docker, slaveId.get(), containerId.get()));
+
+ driver.stop();
+ driver.join();
+
+ delete dockerContainerizer2;
+}
+
+
+// This test verifies that port mapping with bridge network is
+// exposing the host port to the container port, by sending data
+// to the host port and receiving it in the container by listening
+// to the mapped container port.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_NC_PortMapping)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ flags.resources = "cpus:1;mem:1024;ports:[10000-10000]";
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ // We skip stopping the docker container because stopping a container
+ // even when it terminated might not flush the logs and we end up
+ // not getting stdout/stderr in our tests.
+ EXPECT_CALL(*mockDocker, stop(_, _, _))
+ .WillRepeatedly(Return(Nothing()));
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_shell(false);
+ command.set_value("nc");
+ command.add_arguments("-l");
+ command.add_arguments("-p");
+ command.add_arguments("1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ dockerInfo.set_network(ContainerInfo::DockerInfo::BRIDGE);
+
+ ContainerInfo::DockerInfo::PortMapping portMapping;
+ portMapping.set_host_port(10000);
+ portMapping.set_container_port(1000);
+
+ dockerInfo.add_port_mappings()->CopyFrom(portMapping);
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ Future<string> directory;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ FutureArg<3>(&directory),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ Future<TaskStatus> statusFinished;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillOnce(FutureArg<1>(&statusFinished))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY(directory);
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+
+ ASSERT_TRUE(
+ exists(docker, slaveId, containerId.get(), ContainerState::RUNNING));
+
+ string uuid = UUID::random().toString();
+
+ // Write uuid to docker mapped host port.
+ Try<process::Subprocess> s = process::subprocess(
+ "echo " + uuid + " | nc localhost 10000");
+
+ ASSERT_SOME(s);
+ AWAIT_READY_FOR(s.get().status(), Seconds(60));
+
+ AWAIT_READY_FOR(statusFinished, Seconds(60));
+ EXPECT_EQ(TASK_FINISHED, statusFinished.get().state());
+
+ // Now check that the proper output is in stdout.
+ Try<string> read = os::read(path::join(directory.get(), "stdout"));
+ ASSERT_SOME(read);
+
+ const vector<string> lines = strings::split(read.get(), "\n");
+
+ // We expect the uuid that is sent to host port to be written
+ // to stdout by the docker container running nc -l.
+ EXPECT_TRUE(containsLine(lines, uuid));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ Shutdown();
+}
+
+
+// This test verifies that sandbox with ':' in the path can still
+// run successfully. This a limitation of the Docker CLI where
+// the volume map parameter treats colons (:) as seperators,
+// and incorrectly seperates the sandbox directory.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_LaunchSandboxWithColon)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ MockDockerContainerizer dockerContainerizer(flags, &fetcher, docker);
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer, flags);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ SlaveID slaveId = offer.slave_id();
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("test:colon");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ Future<TaskStatus> statusRunning;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusRunning))
+ .WillRepeatedly(DoDefault());
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+ AWAIT_READY_FOR(statusRunning, Seconds(60));
+ EXPECT_EQ(TASK_RUNNING, statusRunning.get().state());
+
+ ASSERT_TRUE(exists(docker, slaveId, containerId.get()));
+
+ Future<containerizer::Termination> termination =
+ dockerContainerizer.wait(containerId.get());
+
+ driver.stop();
+ driver.join();
+
+ AWAIT_READY(termination);
+
+ Shutdown();
+}
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_DestroyWhileFetching)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Promise<Nothing> promise;
+ Future<Nothing> fetch;
+
+ // We want to pause the fetch call to simulate a long fetch time.
+ EXPECT_CALL(*process, fetch(_, _))
+ .WillOnce(DoAll(FutureSatisfy(&fetch),
+ Return(promise.future())));
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFailed));
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+
+ AWAIT_READY(fetch);
+
+ dockerContainerizer.destroy(containerId.get());
+
+ // Resume docker launch.
+ promise.set(Nothing());
+
+ AWAIT_READY(statusFailed);
+
+ EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_DestroyWhilePulling)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Future<Nothing> fetch;
+ EXPECT_CALL(*process, fetch(_, _))
+ .WillOnce(DoAll(FutureSatisfy(&fetch),
+ Return(Nothing())));
+
+ Promise<Nothing> promise;
+
+ // We want to pause the fetch call to simulate a long fetch time.
+ EXPECT_CALL(*process, pull(_))
+ .WillOnce(Return(promise.future()));
+
+ Try<PID<Slave> > slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer> > offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("sleep 1000");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFailed));
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+
+ // Wait until fetch is finished.
+ AWAIT_READY(fetch);
+
+ dockerContainerizer.destroy(containerId.get());
+
+ // Resume docker launch.
+ promise.set(Nothing());
+
+ AWAIT_READY(statusFailed);
+
+ EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
+
+ driver.stop();
+ driver.join();
+
+ Shutdown();
+}
+
+
+// This test checks that when a docker containerizer update failed
+// and the container failed before the executor started, the executor
+// is properly killed and cleaned up.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_ExecutorCleanupWhenLaunchFailed)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("ls");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFailed));
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ // Fail the update so we don't proceed to send run task to the executor.
+ EXPECT_CALL(dockerContainerizer, update(_, _))
+ .WillRepeatedly(Return(Failure("Fail resource update")));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+
+ AWAIT_READY(statusFailed);
+
+ EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
+
+ driver.stop();
+ driver.join();
+
+ // We expect the executor to have exited, and if not in Shutdown
+ // the test will fail because of the executor process still running.
+ Shutdown();
+}
+
+
+// When the fetch fails we should send the scheduler a status
+// update with message the shows the actual error.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_FetchFailure)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("ls");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFailed));
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ EXPECT_CALL(*process, fetch(_, _))
+ .WillOnce(Return(Failure("some error from fetch")));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+
+ AWAIT_READY(statusFailed);
+
+ EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
+ EXPECT_EQ("Failed to launch container: some error from fetch",
+ statusFailed.get().message());
+
+ // TODO(jaybuff): When MESOS-2035 is addressed we should validate
+ // that statusFailed.get().reason() is correctly set here.
+
+ driver.stop();
+ driver.join();
+
+ // We expect the executor to have exited, and if not in Shutdown
+ // the test will fail because of the executor process still running.
+ Shutdown();
+}
+
+
+// When the docker pull fails we should send the scheduler a status
+// update with message the shows the actual error.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_DockerPullFailure)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ CommandInfo command;
+ command.set_value("ls");
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("busybox");
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+
+ task.mutable_command()->CopyFrom(command);
+ task.mutable_container()->CopyFrom(containerInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFailed));
+
+ Future<ContainerID> containerId;
+ EXPECT_CALL(dockerContainerizer, launch(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(FutureArg<0>(&containerId),
+ Invoke(&dockerContainerizer,
+ &MockDockerContainerizer::_launch)));
+
+ EXPECT_CALL(*mockDocker, pull(_, _, _))
+ .WillOnce(Return(Failure("some error from docker pull")));
+
+ driver.launchTasks(offers.get()[0].id(), {task});
+
+ AWAIT_READY_FOR(containerId, Seconds(60));
+
+ AWAIT_READY(statusFailed);
+
+ EXPECT_EQ(TASK_FAILED, statusFailed.get().state());
+ EXPECT_EQ("Failed to launch container: some error from docker pull",
+ statusFailed.get().message());
+
+ // TODO(jaybuff): When MESOS-2035 is addressed we should validate
+ // that statusFailed.get().reason() is correctly set here.
+
+ driver.stop();
+ driver.join();
+
+ // We expect the executor to have exited, and if not in Shutdown
+ // the test will fail because of the executor process still running.
+ Shutdown();
+}
+
+
+// When the docker executor container fails to launch, docker inspect
+// future that is in a retry loop should be discarded.
+TEST_F(DockerContainerizerTest, ROOT_DOCKER_DockerInspectDiscard)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ MockDocker* mockDocker = new MockDocker(tests::flags.docker);
+ Shared<Docker> docker(mockDocker);
+
+ Future<Docker::Container> inspect;
+ EXPECT_CALL(*mockDocker, inspect(_, _))
+ .WillOnce(FutureResult(&inspect,
+ Invoke((MockDocker*) docker.get(),
+ &MockDocker::_inspect)));
+
+ EXPECT_CALL(*mockDocker, run(_, _, _, _, _, _, _, _, _))
+ .WillOnce(Return(Failure("Run failed")));
+
+ Fetcher fetcher;
+
+ // The docker containerizer will free the process, so we must
+ // allocate on the heap.
+ MockDockerContainerizerProcess* process =
+ new MockDockerContainerizerProcess(flags, &fetcher, docker);
+
+ MockDockerContainerizer dockerContainerizer(
+ (Owned<DockerContainerizerProcess>(process)));
+
+ Try<PID<Slave>> slave = StartSlave(&dockerContainerizer);
+ ASSERT_SOME(slave);
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+ Future<FrameworkID> frameworkId;
+ EXPECT_CALL(sched, registered(&driver, _, _))
+ .WillOnce(FutureArg<1>(&frameworkId));
+
+ Future<vector<Offer>> offers;
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillOnce(FutureArg<1>(&offers))
+ .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+ driver.start();
+
+ AWAIT_READY(frameworkId);
+
+ AWAIT_READY(offers);
+ ASSERT_NE(0u, offers.get().size());
+
+ const Offer& offer = offers.get()[0];
+
+ TaskInfo task;
+ task.set_name("");
+ task.mutable_task_id()->set_value("1");
+ task.mutable_slave_id()->CopyFrom(offer.slave_id());
+ task.mutable_resources()->CopyFrom(offer.resources());
+
+ ExecutorInfo executorInfo;
+ ExecutorID executorId;
+ executorId.set_value("e1");
+ executorInfo.mutable_executor_id()->CopyFrom(executorId);
+
+ CommandInfo command;
+ command.set_value("/bin/test-executor");
+ executorInfo.mutable_command()->CopyFrom(command);
+
+ ContainerInfo containerInfo;
+ containerInfo.set_type(ContainerInfo::DOCKER);
+
+ // TODO(tnachen): Use local image to test if possible.
+ ContainerInfo::DockerInfo dockerInfo;
+ dockerInfo.set_image("tnachen/test-executor");
+
+ containerInfo.mutable_docker()->CopyFrom(dockerInfo);
+ executorInfo.mutable_container()->CopyFrom(containerInfo);
+
+ task.mutable_executor()->CopyFrom(executorInfo);
+
+ Future<TaskStatus> statusFailed;
+ EXPECT_CALL(sched, statusUpdate(&driver, _))
+ .WillOnce(FutureArg<1>(&statusFaile
<TRUNCATED>
[12/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
Moved containerizer related tests under src/tests/containerizer.
Review: https://reviews.apache.org/r/36801
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/96351372
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/96351372
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/96351372
Branch: refs/heads/master
Commit: 963513722ecf121cc9ec8c1d4b63922398fc0658
Parents: 50696fa
Author: Jie Yu <yu...@gmail.com>
Authored: Fri Jul 24 15:42:43 2015 -0700
Committer: Jie Yu <yu...@gmail.com>
Committed: Fri Jul 24 16:37:23 2015 -0700
----------------------------------------------------------------------
src/Makefile.am | 174 +-
src/tests/cgroups_isolator_tests.cpp | 46 -
src/tests/cgroups_tests.cpp | 1234 --------
src/tests/composing_containerizer_tests.cpp | 171 -
.../containerizer/cgroups_isolator_tests.cpp | 46 +
src/tests/containerizer/cgroups_tests.cpp | 1235 ++++++++
.../composing_containerizer_tests.cpp | 171 +
src/tests/containerizer/containerizer_tests.cpp | 732 +++++
.../docker_containerizer_tests.cpp | 2955 ++++++++++++++++++
src/tests/containerizer/docker_tests.cpp | 421 +++
.../external_containerizer_test.cpp | 267 ++
src/tests/containerizer/fs_tests.cpp | 170 +
src/tests/containerizer/isolator.hpp | 101 +
src/tests/containerizer/isolator_tests.cpp | 1317 ++++++++
src/tests/containerizer/launch_tests.cpp | 238 ++
src/tests/containerizer/launcher.hpp | 119 +
.../containerizer/memory_pressure_tests.cpp | 293 ++
src/tests/containerizer/memory_test_helper.cpp | 321 ++
src/tests/containerizer/memory_test_helper.hpp | 89 +
.../containerizer/memory_test_helper_main.cpp | 32 +
src/tests/containerizer/ns_tests.cpp | 302 ++
src/tests/containerizer/perf_tests.cpp | 183 ++
src/tests/containerizer/port_mapping_tests.cpp | 2296 ++++++++++++++
src/tests/containerizer/routing_tests.cpp | 1416 +++++++++
src/tests/containerizer/sched_tests.cpp | 96 +
src/tests/containerizer/setns_test_helper.cpp | 63 +
src/tests/containerizer/setns_test_helper.hpp | 35 +
.../containerizer/setns_test_helper_main.cpp | 30 +
src/tests/containerizer_tests.cpp | 731 -----
src/tests/docker_containerizer_tests.cpp | 2955 ------------------
src/tests/docker_tests.cpp | 421 ---
src/tests/external_containerizer_test.cpp | 266 --
src/tests/fs_tests.cpp | 170 -
src/tests/isolator.hpp | 101 -
src/tests/isolator_tests.cpp | 1316 --------
src/tests/launch_tests.cpp | 238 --
src/tests/launcher.hpp | 119 -
src/tests/memory_pressure_tests.cpp | 293 --
src/tests/memory_test_helper.cpp | 320 --
src/tests/memory_test_helper.hpp | 89 -
src/tests/memory_test_helper_main.cpp | 32 -
src/tests/ns_tests.cpp | 301 --
src/tests/perf_tests.cpp | 183 --
src/tests/port_mapping_tests.cpp | 2296 --------------
src/tests/routing_tests.cpp | 1416 ---------
src/tests/sched_tests.cpp | 96 -
src/tests/setns_test_helper.cpp | 63 -
src/tests/setns_test_helper.hpp | 35 -
src/tests/setns_test_helper_main.cpp | 30 -
49 files changed, 13015 insertions(+), 13009 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 93a6a7a..a7104bb 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -658,17 +658,17 @@ libmesos_no_3rdparty_la_SOURCES += \
tests/containerizer.hpp \
tests/environment.hpp \
tests/flags.hpp \
- tests/isolator.hpp \
- tests/launcher.hpp \
tests/limiter.hpp \
- tests/memory_test_helper.hpp \
tests/mesos.hpp \
tests/module.hpp \
tests/script.hpp \
- tests/setns_test_helper.hpp \
tests/utils.hpp \
tests/zookeeper.hpp \
tests/zookeeper_test_server.hpp \
+ tests/containerizer/isolator.hpp \
+ tests/containerizer/launcher.hpp \
+ tests/containerizer/memory_test_helper.hpp \
+ tests/containerizer/setns_test_helper.hpp \
usage/usage.hpp \
watcher/whitelist_watcher.hpp \
zookeeper/authentication.hpp \
@@ -1383,8 +1383,8 @@ persistent_volume_framework_LDADD = libmesos.la $(LDADD)
if OS_LINUX
check_PROGRAMS += setns-test-helper
setns_test_helper_SOURCES = \
- tests/setns_test_helper_main.cpp \
- tests/setns_test_helper.cpp
+ tests/containerizer/setns_test_helper_main.cpp \
+ tests/containerizer/setns_test_helper.cpp
setns_test_helper_CPPFLAGS = $(MESOS_CPPFLAGS)
setns_test_helper_LDADD = libmesos.la $(LDADD)
@@ -1393,8 +1393,8 @@ endif
check_PROGRAMS += memory-test-helper
memory_test_helper_SOURCES = \
tests/flags.cpp \
- tests/memory_test_helper_main.cpp \
- tests/memory_test_helper.cpp
+ tests/containerizer/memory_test_helper_main.cpp \
+ tests/containerizer/memory_test_helper.cpp
memory_test_helper_CPPFLAGS = $(mesos_tests_CPPFLAGS)
memory_test_helper_LDADD = libmesos.la $(LDADD)
@@ -1474,74 +1474,74 @@ libtestqos_controller_la_SOURCES = \
libtestqos_controller_la_CPPFLAGS = $(MESOS_CPPFLAGS)
libtestqos_controller_la_LDFLAGS = $(MESOS_TEST_MODULE_LDFLAGS)
-mesos_tests_SOURCES = \
- tests/anonymous_tests.cpp \
- tests/attributes_tests.cpp \
- tests/authentication_tests.cpp \
- tests/authorization_tests.cpp \
- tests/common/http_tests.cpp \
- tests/composing_containerizer_tests.cpp \
- tests/containerizer.cpp \
- tests/containerizer_tests.cpp \
- tests/cram_md5_authentication_tests.cpp \
- tests/credentials_tests.cpp \
- tests/disk_quota_tests.cpp \
- tests/docker_containerizer_tests.cpp \
- tests/docker_tests.cpp \
- tests/environment.cpp \
- tests/examples_tests.cpp \
- tests/exception_tests.cpp \
- tests/external_containerizer_test.cpp \
- tests/health_check_tests.cpp \
- tests/fault_tolerance_tests.cpp \
- tests/fetcher_cache_tests.cpp \
- tests/fetcher_tests.cpp \
- tests/files_tests.cpp \
- tests/flags.cpp \
- tests/gc_tests.cpp \
- tests/hierarchical_allocator_tests.cpp \
- tests/hook_tests.cpp \
- tests/http_api_tests.cpp \
- tests/isolator_tests.cpp \
- tests/log_tests.cpp \
- tests/logging_tests.cpp \
- tests/main.cpp \
- tests/master_allocator_tests.cpp \
- tests/master_authorization_tests.cpp \
- tests/master_contender_detector_tests.cpp \
- tests/master_slave_reconciliation_tests.cpp \
- tests/master_tests.cpp \
- tests/master_validation_tests.cpp \
- tests/memory_test_helper.cpp \
- tests/mesos.cpp \
- tests/metrics_tests.cpp \
- tests/module.cpp \
- tests/module_tests.cpp \
- tests/monitor_tests.cpp \
- tests/oversubscription_tests.cpp \
- tests/partition_tests.cpp \
- tests/paths_tests.cpp \
- tests/persistent_volume_tests.cpp \
- tests/protobuf_io_tests.cpp \
- tests/rate_limiting_tests.cpp \
- tests/reconciliation_tests.cpp \
- tests/registrar_tests.cpp \
- tests/repair_tests.cpp \
- tests/reservation_tests.cpp \
- tests/resource_offers_tests.cpp \
- tests/resources_tests.cpp \
- tests/scheduler_tests.cpp \
- tests/scheduler_event_call_tests.cpp \
- tests/script.cpp \
- tests/slave_recovery_tests.cpp \
- tests/slave_tests.cpp \
- tests/sorter_tests.cpp \
- tests/state_tests.cpp \
- tests/status_update_manager_tests.cpp \
- tests/teardown_tests.cpp \
- tests/utils.cpp \
- tests/values_tests.cpp \
- tests/zookeeper_url_tests.cpp
+mesos_tests_SOURCES = \
+ tests/anonymous_tests.cpp \
+ tests/attributes_tests.cpp \
+ tests/authentication_tests.cpp \
+ tests/authorization_tests.cpp \
+ tests/containerizer.cpp \
+ tests/cram_md5_authentication_tests.cpp \
+ tests/credentials_tests.cpp \
+ tests/disk_quota_tests.cpp \
+ tests/environment.cpp \
+ tests/examples_tests.cpp \
+ tests/exception_tests.cpp \
+ tests/health_check_tests.cpp \
+ tests/fault_tolerance_tests.cpp \
+ tests/fetcher_cache_tests.cpp \
+ tests/fetcher_tests.cpp \
+ tests/files_tests.cpp \
+ tests/flags.cpp \
+ tests/gc_tests.cpp \
+ tests/hierarchical_allocator_tests.cpp \
+ tests/hook_tests.cpp \
+ tests/http_api_tests.cpp \
+ tests/log_tests.cpp \
+ tests/logging_tests.cpp \
+ tests/main.cpp \
+ tests/master_allocator_tests.cpp \
+ tests/master_authorization_tests.cpp \
+ tests/master_contender_detector_tests.cpp \
+ tests/master_slave_reconciliation_tests.cpp \
+ tests/master_tests.cpp \
+ tests/master_validation_tests.cpp \
+ tests/mesos.cpp \
+ tests/metrics_tests.cpp \
+ tests/module.cpp \
+ tests/module_tests.cpp \
+ tests/monitor_tests.cpp \
+ tests/oversubscription_tests.cpp \
+ tests/partition_tests.cpp \
+ tests/paths_tests.cpp \
+ tests/persistent_volume_tests.cpp \
+ tests/protobuf_io_tests.cpp \
+ tests/rate_limiting_tests.cpp \
+ tests/reconciliation_tests.cpp \
+ tests/registrar_tests.cpp \
+ tests/repair_tests.cpp \
+ tests/reservation_tests.cpp \
+ tests/resource_offers_tests.cpp \
+ tests/resources_tests.cpp \
+ tests/scheduler_tests.cpp \
+ tests/scheduler_event_call_tests.cpp \
+ tests/script.cpp \
+ tests/slave_recovery_tests.cpp \
+ tests/slave_tests.cpp \
+ tests/sorter_tests.cpp \
+ tests/state_tests.cpp \
+ tests/status_update_manager_tests.cpp \
+ tests/teardown_tests.cpp \
+ tests/utils.cpp \
+ tests/values_tests.cpp \
+ tests/zookeeper_url_tests.cpp \
+ tests/common/http_tests.cpp \
+ tests/containerizer/composing_containerizer_tests.cpp \
+ tests/containerizer/containerizer_tests.cpp \
+ tests/containerizer/docker_containerizer_tests.cpp \
+ tests/containerizer/docker_tests.cpp \
+ tests/containerizer/external_containerizer_test.cpp \
+ tests/containerizer/isolator_tests.cpp \
+ tests/containerizer/memory_test_helper.cpp
mesos_tests_CPPFLAGS = $(MESOS_CPPFLAGS)
mesos_tests_CPPFLAGS += -DSOURCE_DIR=\"$(abs_top_srcdir)\"
@@ -1554,20 +1554,20 @@ mesos_tests_LDADD = ../$(LIBPROCESS)/3rdparty/libgmock.la libmesos.la -ldl $(LDA
mesos_tests_DEPENDENCIES = # Initialized to allow += below.
if OS_LINUX
- mesos_tests_SOURCES += tests/cgroups_isolator_tests.cpp
- mesos_tests_SOURCES += tests/cgroups_tests.cpp
- mesos_tests_SOURCES += tests/fs_tests.cpp
- mesos_tests_SOURCES += tests/launch_tests.cpp
- mesos_tests_SOURCES += tests/memory_pressure_tests.cpp
- mesos_tests_SOURCES += tests/ns_tests.cpp
- mesos_tests_SOURCES += tests/perf_tests.cpp
- mesos_tests_SOURCES += tests/sched_tests.cpp
- mesos_tests_SOURCES += tests/setns_test_helper.cpp
+ mesos_tests_SOURCES += tests/containerizer/cgroups_isolator_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/cgroups_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/fs_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/launch_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/memory_pressure_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/ns_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/perf_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/sched_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/setns_test_helper.cpp
endif
if WITH_NETWORK_ISOLATOR
- mesos_tests_SOURCES += tests/routing_tests.cpp
- mesos_tests_SOURCES += tests/port_mapping_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/routing_tests.cpp
+ mesos_tests_SOURCES += tests/containerizer/port_mapping_tests.cpp
endif
if HAS_JAVA
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/cgroups_isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/cgroups_isolator_tests.cpp b/src/tests/cgroups_isolator_tests.cpp
deleted file mode 100644
index a4ccc8e..0000000
--- a/src/tests/cgroups_isolator_tests.cpp
+++ /dev/null
@@ -1,46 +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 <map>
-#include <utility>
-
-#include <gtest/gtest.h>
-
-#include <stout/foreach.hpp>
-#include <stout/proc.hpp>
-#include <stout/stringify.hpp>
-
-#include "slave/containerizer/mesos/containerizer.hpp"
-
-#include "tests/script.hpp"
-
-using std::map;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-// Run the balloon framework under a mesos containerizer.
-TEST_SCRIPT(ContainerizerTest,
- ROOT_CGROUPS_BalloonFramework,
- "balloon_framework_test.sh")
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/cgroups_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/cgroups_tests.cpp b/src/tests/cgroups_tests.cpp
deleted file mode 100644
index b63d956..0000000
--- a/src/tests/cgroups_tests.cpp
+++ /dev/null
@@ -1,1234 +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 <assert.h>
-#include <errno.h>
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <set>
-#include <string>
-#include <vector>
-
-#include <sys/mman.h>
-#include <sys/ptrace.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include <gmock/gmock.h>
-
-#include <process/gtest.hpp>
-#include <process/owned.hpp>
-
-#include <stout/gtest.hpp>
-#include <stout/hashmap.hpp>
-#include <stout/numify.hpp>
-#include <stout/option.hpp>
-#include <stout/os.hpp>
-#include <stout/path.hpp>
-#include <stout/proc.hpp>
-#include <stout/stringify.hpp>
-#include <stout/strings.hpp>
-
-#include "linux/cgroups.hpp"
-#include "linux/perf.hpp"
-
-#include "tests/memory_test_helper.hpp"
-#include "tests/mesos.hpp" // For TEST_CGROUPS_(HIERARCHY|ROOT).
-#include "tests/utils.hpp"
-
-using namespace process;
-
-using cgroups::memory::pressure::Level;
-using cgroups::memory::pressure::Counter;
-
-using std::set;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-class CgroupsTest : public TemporaryDirectoryTest
-{
-public:
- static void SetUpTestCase()
- {
- // Clean up the testing hierarchy, in case it wasn't cleaned up
- // properly from previous tests.
- AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
- }
-
- static void TearDownTestCase()
- {
- AWAIT_READY(cgroups::cleanup(TEST_CGROUPS_HIERARCHY));
- }
-};
-
-
-// A fixture which is used to name tests that expect NO hierarchy to
-// exist in order to test the ability to create a hierarchy (since
-// most likely existing hierarchies will have all or most subsystems
-// attached rendering our ability to create a hierarchy fruitless).
-class CgroupsNoHierarchyTest : public CgroupsTest
-{
-public:
- static void SetUpTestCase()
- {
- CgroupsTest::SetUpTestCase();
-
- Try<std::set<std::string> > hierarchies = cgroups::hierarchies();
- ASSERT_SOME(hierarchies);
- ASSERT_TRUE(hierarchies.get().empty())
- << "-------------------------------------------------------------\n"
- << "We cannot run any cgroups tests that require mounting\n"
- << "hierarchies because you have the following hierarchies mounted:\n"
- << strings::trim(stringify(hierarchies.get()), " {},") << "\n"
- << "You can either unmount those hierarchies, or disable\n"
- << "this test case (i.e., --gtest_filter=-CgroupsNoHierarchyTest.*).\n"
- << "-------------------------------------------------------------";
- }
-};
-
-
-// A fixture that assumes ANY hierarchy is acceptable for use provided
-// it has the subsystems attached that were specified in the
-// constructor. If no hierarchy could be found that has all the
-// required subsystems then we attempt to create a new hierarchy.
-class CgroupsAnyHierarchyTest : public CgroupsTest
-{
-public:
- CgroupsAnyHierarchyTest(const std::string& _subsystems = "cpu")
- : subsystems(_subsystems) {}
-
-protected:
- virtual void SetUp()
- {
- CgroupsTest::SetUp();
-
- foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
- // Establish the base hierarchy if this is the first subsystem checked.
- if (baseHierarchy.empty()) {
- Result<std::string> hierarchy = cgroups::hierarchy(subsystem);
- ASSERT_FALSE(hierarchy.isError());
-
- if (hierarchy.isNone()) {
- baseHierarchy = TEST_CGROUPS_HIERARCHY;
- } else {
- // Strip the subsystem to get the base hierarchy.
- Try<std::string> baseDirname = Path(hierarchy.get()).dirname();
- ASSERT_SOME(baseDirname);
- baseHierarchy = baseDirname.get();
- }
- }
-
- // Mount the subsystem if necessary.
- std::string hierarchy = path::join(baseHierarchy, subsystem);
- Try<bool> mounted = cgroups::mounted(hierarchy, subsystem);
- ASSERT_SOME(mounted);
- if (!mounted.get()) {
- ASSERT_SOME(cgroups::mount(hierarchy, subsystem))
- << "-------------------------------------------------------------\n"
- << "We cannot run any cgroups tests that require\n"
- << "a hierarchy with subsystem '" << subsystem << "'\n"
- << "because we failed to find an existing hierarchy\n"
- << "or create a new one (tried '" << hierarchy << "').\n"
- << "You can either remove all existing\n"
- << "hierarchies, or disable this test case\n"
- << "(i.e., --gtest_filter=-"
- << ::testing::UnitTest::GetInstance()
- ->current_test_info()
- ->test_case_name() << ".*).\n"
- << "-------------------------------------------------------------";
- }
-
- Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
- CHECK_SOME(cgroups);
-
- foreach (const std::string& cgroup, cgroups.get()) {
- // Remove any cgroups that start with TEST_CGROUPS_ROOT.
- if (cgroup == TEST_CGROUPS_ROOT) {
- AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
- }
- }
- }
- }
-
- virtual void TearDown()
- {
- // Remove all *our* cgroups.
- foreach (const std::string& subsystem, strings::tokenize(subsystems, ",")) {
- std::string hierarchy = path::join(baseHierarchy, subsystem);
-
- Try<std::vector<std::string> > cgroups = cgroups::get(hierarchy);
- CHECK_SOME(cgroups);
-
- foreach (const std::string& cgroup, cgroups.get()) {
- // Remove any cgroups that start with TEST_CGROUPS_ROOT.
- if (cgroup == TEST_CGROUPS_ROOT) {
- AWAIT_READY(cgroups::destroy(hierarchy, cgroup));
- }
- }
- }
-
- CgroupsTest::TearDown();
- }
-
- const std::string subsystems; // Subsystems required to run tests.
- std::string baseHierarchy; // Path to the hierarchy being used.
-};
-
-
-class CgroupsAnyHierarchyWithCpuMemoryTest
- : public CgroupsAnyHierarchyTest
-{
-public:
- CgroupsAnyHierarchyWithCpuMemoryTest()
- : CgroupsAnyHierarchyTest("cpu,memory") {}
-};
-
-
-class CgroupsAnyHierarchyWithFreezerTest
- : public CgroupsAnyHierarchyTest
-{
-public:
- CgroupsAnyHierarchyWithFreezerTest()
- : CgroupsAnyHierarchyTest("freezer") {}
-};
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Enabled)
-{
- EXPECT_SOME_TRUE(cgroups::enabled(""));
- EXPECT_SOME_TRUE(cgroups::enabled(","));
- EXPECT_SOME_TRUE(cgroups::enabled("cpu"));
- EXPECT_SOME_TRUE(cgroups::enabled(",cpu"));
- EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory"));
- EXPECT_SOME_TRUE(cgroups::enabled("cpu,memory,"));
- EXPECT_ERROR(cgroups::enabled("invalid"));
- EXPECT_ERROR(cgroups::enabled("cpu,invalid"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Busy)
-{
- EXPECT_SOME_FALSE(cgroups::busy(""));
- EXPECT_SOME_FALSE(cgroups::busy(","));
- EXPECT_SOME_TRUE(cgroups::busy("cpu"));
- EXPECT_SOME_TRUE(cgroups::busy(",cpu"));
- EXPECT_SOME_TRUE(cgroups::busy("cpu,memory"));
- EXPECT_SOME_TRUE(cgroups::busy("cpu,memory,"));
- EXPECT_ERROR(cgroups::busy("invalid"));
- EXPECT_ERROR(cgroups::busy("cpu,invalid"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Subsystems)
-{
- Try<std::set<std::string> > names = cgroups::subsystems();
- ASSERT_SOME(names);
-
- Option<std::string> cpu;
- Option<std::string> memory;
- foreach (const std::string& name, names.get()) {
- if (name == "cpu") {
- cpu = name;
- } else if (name == "memory") {
- memory = name;
- }
- }
-
- EXPECT_SOME(cpu);
- EXPECT_SOME(memory);
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_SubsystemsHierarchy)
-{
- std::string cpuHierarchy = path::join(baseHierarchy, "cpu");
-
- Try<std::set<std::string> > names = cgroups::subsystems(cpuHierarchy);
- ASSERT_SOME(names);
-
- Option<std::string> cpu;
- Option<std::string> memory;
- foreach (const std::string& name, names.get()) {
- if (name == "cpu") {
- cpu = name;
- } else if (name == "memory") {
- memory = name;
- }
- }
-
- EXPECT_SOME(cpu);
- EXPECT_NONE(memory);
-
- std::string memoryHierarchy = path::join(baseHierarchy, "memory");
- names = cgroups::subsystems(memoryHierarchy);
- ASSERT_SOME(names);
-
- cpu = None();
- memory = None();
- foreach (const std::string& name, names.get()) {
- if (name == "cpu") {
- cpu = name;
- } else if (name == "memory") {
- memory = name;
- }
- }
- EXPECT_NONE(cpu);
- EXPECT_SOME(memory);
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FindCgroupSubsystems)
-{
- pid_t pid = ::getpid();
- Result<std::string> cpuHierarchy = cgroups::cpu::cgroup(pid);
- EXPECT_FALSE(cpuHierarchy.isError());
- EXPECT_SOME(cpuHierarchy);
-
- Result<std::string> memHierarchy = cgroups::memory::cgroup(pid);
- EXPECT_FALSE(memHierarchy.isError());
- EXPECT_SOME(memHierarchy);
-}
-
-
-TEST_F(CgroupsNoHierarchyTest, ROOT_CGROUPS_NOHIERARCHY_MountUnmountHierarchy)
-{
- EXPECT_ERROR(cgroups::mount("/tmp", "cpu"));
- EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "invalid"));
-
- // Try to mount a valid hierarchy, retrying as necessary since the
- // previous unmount might not have taken effect yet due to a bug in
- // Ubuntu 12.04.
- ASSERT_SOME(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpu,memory", 10));
- EXPECT_ERROR(cgroups::mount(TEST_CGROUPS_HIERARCHY, "cpuset"));
- EXPECT_ERROR(cgroups::unmount("/tmp"));
- ASSERT_SOME(cgroups::unmount(TEST_CGROUPS_HIERARCHY));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Mounted)
-{
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist"));
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp"));
- EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected"));
- EXPECT_SOME_TRUE(cgroups::mounted(baseHierarchy + "/cpu"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_MountedSubsystems)
-{
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp-nonexist", "cpu"));
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu,memory"));
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "cpu"));
- EXPECT_SOME_FALSE(cgroups::mounted("/tmp", "invalid"));
- EXPECT_SOME_TRUE(cgroups::mounted(path::join(baseHierarchy, "cpu"), "cpu"));
- EXPECT_SOME_TRUE(cgroups::mounted(
- path::join(baseHierarchy, "memory"), "memory"));
- EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy, "invalid"));
- EXPECT_SOME_FALSE(cgroups::mounted(baseHierarchy + "/not_expected", "cpu"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_CreateRemove)
-{
- EXPECT_ERROR(cgroups::create("/tmp", "test"));
- EXPECT_ERROR(cgroups::create(baseHierarchy, "mesos_test_missing/1"));
- ASSERT_SOME(cgroups::create(
- path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
- EXPECT_ERROR(cgroups::remove(baseHierarchy, "invalid"));
- ASSERT_SOME(cgroups::remove(
- path::join(baseHierarchy, "cpu"), "mesos_test_missing"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Get)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
-
- ASSERT_SOME(cgroups::create(hierarchy, "mesos_test1"));
- ASSERT_SOME(cgroups::create(hierarchy, "mesos_test2"));
-
- Try<std::vector<std::string>> cgroups = cgroups::get(hierarchy);
- ASSERT_SOME(cgroups);
-
- EXPECT_NE(cgroups.get().end(),
- find(cgroups.get().begin(), cgroups.get().end(), "mesos_test2"));
- EXPECT_NE(cgroups.get().end(),
- find(cgroups.get().begin(), cgroups.get().end(), "mesos_test1"));
-
- ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test1"));
- ASSERT_SOME(cgroups::remove(hierarchy, "mesos_test2"));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_NestedCgroups)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
- std::string cgroup1 = path::join(TEST_CGROUPS_ROOT, "1");
- std::string cgroup2 = path::join(TEST_CGROUPS_ROOT, "2");
-
- ASSERT_SOME(cgroups::create(hierarchy, cgroup1))
- << "-------------------------------------------------------------\n"
- << "We cannot run this test because it appears you do not have\n"
- << "a modern enough version of the Linux kernel. You won't be\n"
- << "able to use the cgroups isolator, but feel free to disable\n"
- << "this test.\n"
- << "-------------------------------------------------------------";
-
- ASSERT_SOME(cgroups::create(hierarchy, cgroup2));
-
- Try<std::vector<std::string>> cgroups =
- cgroups::get(hierarchy, TEST_CGROUPS_ROOT);
- ASSERT_SOME(cgroups);
-
- ASSERT_EQ(2u, cgroups.get().size());
-
- EXPECT_NE(cgroups.get().end(),
- find(cgroups.get().begin(), cgroups.get().end(), cgroup2));
- EXPECT_NE(cgroups.get().end(),
- find(cgroups.get().begin(), cgroups.get().end(), cgroup1));
-
- ASSERT_SOME(cgroups::remove(hierarchy, cgroup1));
- ASSERT_SOME(cgroups::remove(hierarchy, cgroup2));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Tasks)
-{
- pid_t pid = ::getpid();
-
- Result<std::string> cgroup = cgroups::cpu::cgroup(pid);
- ASSERT_SOME(cgroup);
-
- std::string hierarchy = path::join(baseHierarchy, "cpu");
-
- Try<std::set<pid_t>> pids = cgroups::processes(hierarchy, cgroup.get());
- ASSERT_SOME(pids);
-
- EXPECT_NE(0u, pids.get().count(pid));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Read)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
-
- EXPECT_ERROR(cgroups::read(hierarchy, TEST_CGROUPS_ROOT, "invalid42"));
-
- pid_t pid = ::getpid();
-
- Result<std::string> cgroup = cgroups::cpu::cgroup(pid);
- ASSERT_SOME(cgroup);
-
- Try<std::string> read = cgroups::read(hierarchy, cgroup.get(), "tasks");
- ASSERT_SOME(read);
-
- EXPECT_TRUE(strings::contains(read.get(), stringify(pid)));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Write)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
- EXPECT_ERROR(
- cgroups::write(hierarchy, TEST_CGROUPS_ROOT, "invalid", "invalid"));
-
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // In child process, wait for kill signal.
- while (true) { sleep(1); }
-
- // Should not reach here.
- const char* message = "Error, child should be killed before reaching here";
- while (write(STDERR_FILENO, message, strlen(message)) == -1 &&
- errno == EINTR);
-
- _exit(1);
- }
-
- // In parent process.
- ASSERT_SOME(
- cgroups::write(hierarchy,
- TEST_CGROUPS_ROOT,
- "cgroup.procs",
- stringify(pid)));
-
- Try<std::set<pid_t> > pids = cgroups::processes(hierarchy, TEST_CGROUPS_ROOT);
- ASSERT_SOME(pids);
-
- EXPECT_NE(0u, pids.get().count(pid));
-
- // Kill the child process.
- ASSERT_NE(-1, ::kill(pid, SIGKILL));
-
- // Wait for the child process.
- int status;
- EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-
-TEST_F(CgroupsAnyHierarchyTest, ROOT_CGROUPS_Cfs_Big_Quota)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- Duration quota = Seconds(100); // Big quota.
- ASSERT_SOME(cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT, quota));
-
- // Ensure we can read back the correct quota.
- ASSERT_SOME_EQ(
- quota,
- cgroups::cpu::cfs_quota_us(hierarchy, TEST_CGROUPS_ROOT));
-}
-
-
-class CgroupsAnyHierarchyWithCpuAcctMemoryTest
- : public CgroupsAnyHierarchyTest
-{
-public:
- CgroupsAnyHierarchyWithCpuAcctMemoryTest()
- : CgroupsAnyHierarchyTest("cpuacct,memory") {}
-};
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_Stat)
-{
- EXPECT_ERROR(cgroups::stat(baseHierarchy, TEST_CGROUPS_ROOT, "invalid"));
-
- Try<hashmap<std::string, uint64_t> > result =
- cgroups::stat(
- path::join(baseHierarchy, "cpuacct"), "/", "cpuacct.stat");
- ASSERT_SOME(result);
- EXPECT_TRUE(result.get().contains("user"));
- EXPECT_TRUE(result.get().contains("system"));
- EXPECT_GT(result.get().get("user").get(), 0llu);
- EXPECT_GT(result.get().get("system").get(), 0llu);
-
- result = cgroups::stat(
- path::join(baseHierarchy, "memory"), "/", "memory.stat");
- ASSERT_SOME(result);
- EXPECT_TRUE(result.get().contains("rss"));
- EXPECT_GT(result.get().get("rss").get(), 0llu);
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_Listen)
-{
- std::string hierarchy = path::join(baseHierarchy, "memory");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
- ASSERT_SOME(
- cgroups::memory::oom::killer::enabled(hierarchy, TEST_CGROUPS_ROOT))
- << "-------------------------------------------------------------\n"
- << "We cannot run this test because it appears you do not have\n"
- << "a modern enough version of the Linux kernel. You won't be\n"
- << "able to use the cgroups isolator, but feel free to disable\n"
- << "this test.\n"
- << "-------------------------------------------------------------";
-
- const Bytes limit = Megabytes(64);
-
- ASSERT_SOME(cgroups::memory::limit_in_bytes(
- hierarchy, TEST_CGROUPS_ROOT, limit));
-
- // Listen on oom events for test cgroup.
- Future<Nothing> future =
- cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
-
- ASSERT_FALSE(future.isFailed());
-
- // Test the cancellation.
- future.discard();
-
- // Test the normal operation below.
- future = cgroups::memory::oom::listen(hierarchy, TEST_CGROUPS_ROOT);
- ASSERT_FALSE(future.isFailed());
-
- MemoryTestHelper helper;
- ASSERT_SOME(helper.spawn());
- ASSERT_SOME(helper.pid());
-
- EXPECT_SOME(cgroups::assign(
- hierarchy, TEST_CGROUPS_ROOT, helper.pid().get()));
-
- // Request more RSS memory in the subprocess than the limit.
- // NOTE: We enable the kernel oom killer in this test. If it were
- // disabled, the subprocess might hang and the following call won't
- // return. By enabling the oom killer, we let the subprocess get
- // killed and expect that an error is returned.
- EXPECT_ERROR(helper.increaseRSS(limit * 2));
-
- AWAIT_READY(future);
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Freeze)
-{
- int pipes[2];
- int dummy;
- ASSERT_NE(-1, ::pipe(pipes));
-
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // In child process.
- ::close(pipes[0]);
-
- // Put self into the test cgroup.
- Try<Nothing> assign =
- cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
-
- if (assign.isError()) {
- std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
- abort();
- }
-
- // Notify the parent.
- if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
- perror("Failed to notify the parent");
- abort();
- }
- ::close(pipes[1]);
-
- // Infinite loop here.
- while (true);
-
- // Should not reach here.
- std::cerr << "Reach an unreachable statement!" << std::endl;
- abort();
- }
-
- // In parent process.
- ::close(pipes[1]);
-
- // Wait until child has assigned the cgroup.
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ::close(pipes[0]);
-
- // Freeze the test cgroup.
- AWAIT_EXPECT_READY(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
-
- // Thaw the test cgroup.
- AWAIT_EXPECT_READY(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
-
- // Kill the child process.
- ASSERT_NE(-1, ::kill(pid, SIGKILL));
-
- // Wait for the child process.
- int status;
- EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithCpuMemoryTest, ROOT_CGROUPS_FreezeNonFreezer)
-{
- std::string hierarchy = path::join(baseHierarchy, "cpu");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- AWAIT_EXPECT_FAILED(cgroups::freezer::freeze(hierarchy, TEST_CGROUPS_ROOT));
- AWAIT_EXPECT_FAILED(cgroups::freezer::thaw(hierarchy, TEST_CGROUPS_ROOT));
-
- // The cgroup is empty so we should still be able to destroy it.
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Kill)
-{
- int pipes[2];
- int dummy;
- ASSERT_NE(-1, ::pipe(pipes));
-
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid > 0) {
- // In parent process.
- ::close(pipes[1]);
-
- // Wait until all children have assigned the cgroup.
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ::close(pipes[0]);
-
- Try<Nothing> kill = cgroups::kill(hierarchy, TEST_CGROUPS_ROOT, SIGKILL);
- EXPECT_SOME(kill);
-
- int status;
- EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
- } else {
- // In child process.
-
- // We create 4 child processes here using two forks to test the case in
- // which there are multiple active processes in the given cgroup.
- ::fork();
- ::fork();
-
- // Put self into the test cgroup.
- Try<Nothing> assign =
- cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
-
- if (assign.isError()) {
- std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
- abort();
- }
-
- // Notify the parent.
- ::close(pipes[0]); // TODO(benh): Close after first fork?
- if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
- perror("Failed to notify the parent");
- abort();
- }
- ::close(pipes[1]);
-
- // Wait kill signal from parent.
- while (true);
-
- // Should not reach here.
- std::cerr << "Reach an unreachable statement!" << std::endl;
- abort();
- }
-}
-
-
-// TODO(benh): Write a version of this test with nested cgroups.
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_Destroy)
-{
- int pipes[2];
- int dummy;
- ASSERT_NE(-1, ::pipe(pipes));
-
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid > 0) {
- // In parent process.
- ::close(pipes[1]);
-
- // Wait until all children have assigned the cgroup.
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ASSERT_LT(0, ::read(pipes[0], &dummy, sizeof(dummy)));
- ::close(pipes[0]);
-
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-
- // cgroups::destroy will reap all processes in the cgroup so we should
- // *not* be able to reap it now.
- int status;
- EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
- EXPECT_EQ(ECHILD, errno);
- } else {
- // In child process.
-
- // We create 4 child processes here using two forks to test the case in
- // which there are multiple active processes in the given cgroup.
- ::fork();
- ::fork();
-
- // Put self into the test cgroup.
- Try<Nothing> assign =
- cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid());
-
- if (assign.isError()) {
- std::cerr << "Failed to assign cgroup: " << assign.error() << std::endl;
- abort();
- }
-
- // Notify the parent.
- ::close(pipes[0]); // TODO(benh): Close after first fork?
- if (::write(pipes[1], &dummy, sizeof(dummy)) != sizeof(dummy)) {
- perror("Failed to notify the parent");
- abort();
- }
- ::close(pipes[1]);
-
- // Wait kill signal from parent.
- while (true) {}
-
- // Should not reach here.
- std::cerr << "Reach an unreachable statement!" << std::endl;
- abort();
- }
-}
-
-
-void* threadFunction(void*)
-{
- // Newly created threads have PTHREAD_CANCEL_ENABLE and
- // PTHREAD_CANCEL_DEFERRED so they can be cancelled from the main thread.
- while (true) { sleep(1); }
-
- return NULL;
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_AssignThreads)
-{
- size_t numThreads = 5;
-
- pthread_t pthreads[numThreads];
-
- // Create additional threads.
- for (size_t i = 0; i < numThreads; i++)
- {
- EXPECT_EQ(0, pthread_create(&pthreads[i], NULL, threadFunction, NULL));
- }
-
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- // Check the test cgroup is initially empty.
- Try<set<pid_t> > cgroupThreads =
- cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
- EXPECT_SOME(cgroupThreads);
- EXPECT_EQ(0u, cgroupThreads.get().size());
-
- // Assign ourselves to the test cgroup.
- CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
-
- // Get our threads (may be more than the numThreads we created if
- // other threads are running).
- Try<set<pid_t> > threads = proc::threads(::getpid());
- ASSERT_SOME(threads);
-
- // Check the test cgroup now only contains all child threads.
- cgroupThreads = cgroups::threads(hierarchy, TEST_CGROUPS_ROOT);
- EXPECT_SOME(cgroupThreads);
- EXPECT_SOME_EQ(threads.get(), cgroupThreads);
-
- // Terminate the additional threads.
- for (size_t i = 0; i < numThreads; i++)
- {
- EXPECT_EQ(0, pthread_cancel(pthreads[i]));
- EXPECT_EQ(0, pthread_join(pthreads[i], NULL));
- }
-
- // Move ourselves to the root cgroup.
- CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
-
- // Destroy the cgroup.
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyStoppedProcess)
-{
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // In child process.
- while (true) { sleep(1); }
-
- ABORT("Child should not reach this statement");
- }
-
- // In parent process.
-
- // Put child into the freezer cgroup.
- Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
-
- // Stop the child process.
- EXPECT_EQ(0, kill(pid, SIGSTOP));
-
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-
- // cgroups::destroy will reap all processes in the cgroup so we should
- // *not* be able to reap it now.
- int status;
- EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
- EXPECT_EQ(ECHILD, errno);
-}
-
-
-TEST_F(CgroupsAnyHierarchyWithFreezerTest, ROOT_CGROUPS_DestroyTracedProcess)
-{
- std::string hierarchy = path::join(baseHierarchy, "freezer");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // In child process.
- while (true) { sleep(1); }
-
- ABORT("Child should not reach this statement");
- }
-
- // In parent process.
- Try<Nothing> assign = cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid);
- ASSERT_SOME(assign);
-
- // Attach to the child process.
- ASSERT_EQ(0, ptrace(PT_ATTACH, pid, NULL, NULL));
-
- // Wait until the process is in traced state ('t' or 'T').
- Duration elapsed = Duration::zero();
- while (true) {
- Result<proc::ProcessStatus> process = proc::status(pid);
- ASSERT_SOME(process);
-
- if (process.get().state == 'T' || process.get().state == 't') {
- break;
- }
-
- if (elapsed > Seconds(1)) {
- FAIL() << "Failed to wait for process to be traced";
- }
-
- os::sleep(Milliseconds(5));
- elapsed += Milliseconds(5);
- }
-
- // Now destroy the cgroup.
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-
- // cgroups::destroy will reap all processes in the cgroup so we should
- // *not* be able to reap it now.
- int status;
- EXPECT_EQ(-1, ::waitpid(pid, &status, 0));
- EXPECT_EQ(ECHILD, errno);
-}
-
-
-class CgroupsAnyHierarchyWithPerfEventTest
- : public CgroupsAnyHierarchyTest
-{
-public:
- CgroupsAnyHierarchyWithPerfEventTest()
- : CgroupsAnyHierarchyTest("perf_event") {}
-};
-
-
-TEST_F(CgroupsAnyHierarchyWithPerfEventTest, ROOT_CGROUPS_Perf)
-{
- int pipes[2];
- int dummy;
- ASSERT_NE(-1, ::pipe(pipes));
-
- std::string hierarchy = path::join(baseHierarchy, "perf_event");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- pid_t pid = ::fork();
- ASSERT_NE(-1, pid);
-
- if (pid == 0) {
- // In child process.
- ::close(pipes[1]);
-
- // Wait until parent has assigned us to the cgroup.
- ssize_t len;
- while ((len = ::read(pipes[0], &dummy, sizeof(dummy))) == -1 &&
- errno == EINTR);
- ASSERT_EQ((ssize_t) sizeof(dummy), len);
- ::close(pipes[0]);
-
- while (true) {
- // Don't sleep so 'perf' can actually sample something.
- }
-
- ABORT("Child should not reach here");
- }
-
- // In parent.
- ::close(pipes[0]);
-
- // Put child into the test cgroup.
- ASSERT_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, pid));
-
- ssize_t len;
- while ((len = ::write(pipes[1], &dummy, sizeof(dummy))) == -1 &&
- errno == EINTR);
- ASSERT_EQ((ssize_t) sizeof(dummy), len);
- ::close(pipes[1]);
-
- std::set<std::string> events;
- // Hardware event.
- events.insert("cycles");
- // Software event.
- events.insert("task-clock");
-
- // NOTE: Wait at least 2 seconds as we've seen some variance in how
- // well 'perf' does across Linux distributions (e.g., Ubuntu 14.04)
- // and we want to make sure that we collect some non-zero values.
- Future<mesos::PerfStatistics> statistics =
- perf::sample(events, TEST_CGROUPS_ROOT, Seconds(2));
- AWAIT_READY(statistics);
-
- ASSERT_TRUE(statistics.get().has_cycles());
-
- // TODO(benh): Some Linux distributions (Ubuntu 14.04) fail to
- // properly sample 'cycles' with 'perf', so we don't explicitly
- // check the value here. See MESOS-3082.
- // EXPECT_LT(0u, statistics.get().cycles());
-
- ASSERT_TRUE(statistics.get().has_task_clock());
- EXPECT_LT(0.0, statistics.get().task_clock());
-
- // Kill the child process.
- ASSERT_NE(-1, ::kill(pid, SIGKILL));
-
- // Wait for the child process.
- int status;
- EXPECT_NE(-1, ::waitpid((pid_t) -1, &status, 0));
- ASSERT_TRUE(WIFSIGNALED(status));
- EXPECT_EQ(SIGKILL, WTERMSIG(status));
-
- // Destroy the cgroup.
- Future<Nothing> destroy = cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT);
- AWAIT_READY(destroy);
-}
-
-
-class CgroupsAnyHierarchyMemoryPressureTest
- : public CgroupsAnyHierarchyTest
-{
-public:
- CgroupsAnyHierarchyMemoryPressureTest()
- : CgroupsAnyHierarchyTest("memory"),
- cgroup(TEST_CGROUPS_ROOT) {}
-
-protected:
- virtual void SetUp()
- {
- CgroupsAnyHierarchyTest::SetUp();
-
- hierarchy = path::join(baseHierarchy, "memory");
-
- ASSERT_SOME(cgroups::create(hierarchy, cgroup));
- }
-
- void listen()
- {
- const std::vector<Level> levels = {
- Level::LOW,
- Level::MEDIUM,
- Level::CRITICAL
- };
-
- foreach (Level level, levels) {
- Try<Owned<Counter>> counter = Counter::create(hierarchy, cgroup, level);
- EXPECT_SOME(counter);
-
- counters[level] = counter.get();
- }
- }
-
- std::string hierarchy;
- const std::string cgroup;
-
- hashmap<Level, Owned<Counter>> counters;
-};
-
-
-TEST_F(CgroupsAnyHierarchyMemoryPressureTest, ROOT_IncreaseUnlockedRSS)
-{
- MemoryTestHelper helper;
- ASSERT_SOME(helper.spawn());
- ASSERT_SOME(helper.pid());
-
- const Bytes limit = Megabytes(16);
-
- // Move the memory test helper into a cgroup and set the limit.
- EXPECT_SOME(cgroups::memory::limit_in_bytes(hierarchy, cgroup, limit));
- EXPECT_SOME(cgroups::assign(hierarchy, cgroup, helper.pid().get()));
-
- listen();
-
- // Used to save the counter readings from last iteration.
- uint64_t previousLow = 0;
- uint64_t previousMedium = 0;
- uint64_t previousCritical = 0;
-
- // Used to save the counter readings from this iteration.
- uint64_t low;
- uint64_t medium;
- uint64_t critical;
-
- // Use a guard to error out if it's been too long.
- // TODO(chzhcn): Use a better way to set testing time limit.
- uint64_t iterationLimit = limit.bytes() / getpagesize() * 10;
-
- for (uint64_t i = 0; i < iterationLimit; i++) {
- EXPECT_SOME(helper.increaseRSS(getpagesize()));
-
- Future<uint64_t> _low = counters[Level::LOW]->value();
- Future<uint64_t> _medium = counters[Level::MEDIUM]->value();
- Future<uint64_t> _critical = counters[Level::CRITICAL]->value();
-
- AWAIT_READY(_low);
- AWAIT_READY(_medium);
- AWAIT_READY(_critical);
-
- low = _low.get();
- medium = _medium.get();
- critical = _critical.get();
-
- // We need to know the readings are the same as last time to be
- // sure they are stable, because the reading is not atomic. For
- // example, the medium could turn positive after we read low to be
- // 0, but this should be fixed by the next read immediately.
- if ((low == previousLow &&
- medium == previousMedium &&
- critical == previousCritical)) {
- if (low != 0) {
- EXPECT_LE(medium, low);
- EXPECT_LE(critical, medium);
-
- // When child's RSS is full, it will be OOM-kill'ed if we
- // don't stop it right away.
- break;
- } else {
- EXPECT_EQ(0u, medium);
- EXPECT_EQ(0u, critical);
- }
- }
-
- previousLow = low;
- previousMedium = medium;
- previousCritical = critical;
- }
-}
-
-
-TEST_F(CgroupsAnyHierarchyMemoryPressureTest, ROOT_IncreasePageCache)
-{
- MemoryTestHelper helper;
- ASSERT_SOME(helper.spawn());
- ASSERT_SOME(helper.pid());
-
- const Bytes limit = Megabytes(16);
-
- // Move the memory test helper into a cgroup and set the limit.
- EXPECT_SOME(cgroups::memory::limit_in_bytes(hierarchy, cgroup, limit));
- EXPECT_SOME(cgroups::assign(hierarchy, cgroup, helper.pid().get()));
-
- listen();
-
- // Used to save the counter readings from last iteration.
- uint64_t previousLow = 0;
- uint64_t previousMedium = 0;
- uint64_t previousCritical = 0;
-
- // Used to save the counter readings from this iteration.
- uint64_t low;
- uint64_t medium;
- uint64_t critical;
-
- // Use a guard to error out if it's been too long.
- // TODO(chzhcn): Use a better way to set testing time limit.
- uint64_t iterationLimit = limit.bytes() / Megabytes(1).bytes() * 2;
-
- for (uint64_t i = 0; i < iterationLimit; i++) {
- EXPECT_SOME(helper.increasePageCache(Megabytes(1)));
-
- Future<uint64_t> _low = counters[Level::LOW]->value();
- Future<uint64_t> _medium = counters[Level::MEDIUM]->value();
- Future<uint64_t> _critical = counters[Level::CRITICAL]->value();
-
- AWAIT_READY(_low);
- AWAIT_READY(_medium);
- AWAIT_READY(_critical);
-
- low = _low.get();
- medium = _medium.get();
- critical = _critical.get();
-
- // We need to know the readings are the same as last time to be
- // sure they are stable, because the reading is not atomic. For
- // example, the medium could turn positive after we read low to be
- // 0, but this should be fixed by the next read immediately.
- if ((low == previousLow &&
- medium == previousMedium &&
- critical == previousCritical)) {
- if (low != 0) {
- EXPECT_LE(medium, low);
- EXPECT_LE(critical, medium);
-
- // Different from the RSS test, since the child is only
- // consuming at a slow rate the page cache, which is evictable
- // and reclaimable, we could therefore be in this state
- // forever. Our guard will let us out shortly.
- } else {
- EXPECT_EQ(0u, medium);
- EXPECT_EQ(0u, critical);
- }
- }
-
- previousLow = low;
- previousMedium = medium;
- previousCritical = critical;
- }
-
- EXPECT_LT(0u, low);
-}
-
-// Tests the cpuacct::stat API. This test just tests for ANY value returned by
-// the API.
-TEST_F(CgroupsAnyHierarchyWithCpuAcctMemoryTest, ROOT_CGROUPS_CpuAcctsStats)
-{
- const std::string hierarchy = path::join(baseHierarchy, "cpuacct");
- ASSERT_SOME(cgroups::create(hierarchy, TEST_CGROUPS_ROOT));
-
- CHECK_SOME(cgroups::assign(hierarchy, TEST_CGROUPS_ROOT, ::getpid()));
-
- ASSERT_SOME(cgroups::cpuacct::stat(hierarchy, TEST_CGROUPS_ROOT));
-
- // Move ourselves to the root cgroup.
- CHECK_SOME(cgroups::assign(hierarchy, "", ::getpid()));
-
- AWAIT_READY(cgroups::destroy(hierarchy, TEST_CGROUPS_ROOT));
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/composing_containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/composing_containerizer_tests.cpp b/src/tests/composing_containerizer_tests.cpp
deleted file mode 100644
index d66f519..0000000
--- a/src/tests/composing_containerizer_tests.cpp
+++ /dev/null
@@ -1,171 +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 <gtest/gtest.h>
-
-#include <vector>
-
-#include <process/future.hpp>
-#include <process/gmock.hpp>
-
-#include <stout/option.hpp>
-
-#include "messages/messages.hpp"
-
-#include "slave/containerizer/containerizer.hpp"
-#include "slave/containerizer/composing.hpp"
-
-#include "tests/mesos.hpp"
-
-using namespace mesos::internal::slave;
-
-using namespace process;
-
-using std::vector;
-
-using testing::_;
-using testing::Return;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-
-class ComposingContainerizerTest : public MesosTest {};
-
-class MockContainerizer : public slave::Containerizer
-{
-public:
- MOCK_METHOD1(
- recover,
- process::Future<Nothing>(
- const Option<slave::state::SlaveState>&));
-
- MOCK_METHOD7(
- launch,
- process::Future<bool>(
- const ContainerID&,
- const ExecutorInfo&,
- const std::string&,
- const Option<std::string>&,
- const SlaveID&,
- const process::PID<Slave>&,
- bool));
-
- MOCK_METHOD8(
- launch,
- process::Future<bool>(
- const ContainerID&,
- const TaskInfo&,
- const ExecutorInfo&,
- const std::string&,
- const Option<std::string>&,
- const SlaveID&,
- const process::PID<Slave>&,
- bool));
-
- MOCK_METHOD2(
- update,
- process::Future<Nothing>(
- const ContainerID&,
- const Resources&));
-
- MOCK_METHOD1(
- usage,
- process::Future<ResourceStatistics>(
- const ContainerID&));
-
- MOCK_METHOD1(
- wait,
- process::Future<containerizer::Termination>(
- const ContainerID&));
-
- MOCK_METHOD1(
- destroy,
- void(const ContainerID&));
-
- MOCK_METHOD0(
- containers,
- process::Future<hashset<ContainerID> >());
-};
-
-
-// This test checks if destroy is called while container is being
-// launched, the composing containerizer still calls the underlying
-// containerizer's destroy and skip calling the rest of the
-// containerizers.
-TEST_F(ComposingContainerizerTest, DestroyWhileLaunching)
-{
- vector<Containerizer*> containerizers;
-
- MockContainerizer* mockContainerizer = new MockContainerizer();
- MockContainerizer* mockContainerizer2 = new MockContainerizer();
-
- containerizers.push_back(mockContainerizer);
- containerizers.push_back(mockContainerizer2);
-
- ComposingContainerizer containerizer(containerizers);
- ContainerID containerId;
- containerId.set_value("container");
- TaskInfo taskInfo;
- ExecutorInfo executorInfo;
- SlaveID slaveId;
- PID<Slave> slavePid;
-
- Promise<bool> launchPromise;
-
- EXPECT_CALL(*mockContainerizer, launch(_, _, _, _, _, _, _, _))
- .WillOnce(Return(launchPromise.future()));
-
- Future<Nothing> destroy;
-
- EXPECT_CALL(*mockContainerizer, destroy(_))
- .WillOnce(FutureSatisfy(&destroy));
-
- Future<bool> launch = containerizer.launch(
- containerId,
- taskInfo,
- executorInfo,
- "dir",
- "user",
- slaveId,
- slavePid,
- false);
-
- Resources resources = Resources::parse("cpus:1;mem:256").get();
-
- EXPECT_TRUE(launch.isPending());
-
- containerizer.destroy(containerId);
-
- EXPECT_CALL(*mockContainerizer2, launch(_, _, _, _, _, _, _, _))
- .Times(0);
-
- // We make sure the destroy is being called on the first containerizer.
- // The second containerizer shouldn't be called as well since the
- // container is already destroyed.
- AWAIT_READY(destroy);
-
- launchPromise.set(false);
- AWAIT_FAILED(launch);
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/cgroups_isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/cgroups_isolator_tests.cpp b/src/tests/containerizer/cgroups_isolator_tests.cpp
new file mode 100644
index 0000000..a4ccc8e
--- /dev/null
+++ b/src/tests/containerizer/cgroups_isolator_tests.cpp
@@ -0,0 +1,46 @@
+/**
+ * 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 <map>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+#include <stout/foreach.hpp>
+#include <stout/proc.hpp>
+#include <stout/stringify.hpp>
+
+#include "slave/containerizer/mesos/containerizer.hpp"
+
+#include "tests/script.hpp"
+
+using std::map;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+// Run the balloon framework under a mesos containerizer.
+TEST_SCRIPT(ContainerizerTest,
+ ROOT_CGROUPS_BalloonFramework,
+ "balloon_framework_test.sh")
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
[06/12] mesos git commit: Moved containerizer related tests under
src/tests/containerizer.
Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/routing_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/routing_tests.cpp b/src/tests/containerizer/routing_tests.cpp
new file mode 100644
index 0000000..e4f1bcf
--- /dev/null
+++ b/src/tests/containerizer/routing_tests.cpp
@@ -0,0 +1,1416 @@
+/**
+ * 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 <signal.h>
+#include <unistd.h>
+
+#include <linux/version.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <gtest/gtest.h>
+
+#include <process/clock.hpp>
+#include <process/gtest.hpp>
+
+#include <stout/foreach.hpp>
+#include <stout/gtest.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/ip.hpp>
+#include <stout/mac.hpp>
+#include <stout/net.hpp>
+#include <stout/stringify.hpp>
+
+#include "linux/routing/handle.hpp"
+#include "linux/routing/route.hpp"
+#include "linux/routing/utils.hpp"
+
+#include "linux/routing/diagnosis/diagnosis.hpp"
+
+#include "linux/routing/filter/basic.hpp"
+#include "linux/routing/filter/handle.hpp"
+#include "linux/routing/filter/icmp.hpp"
+#include "linux/routing/filter/ip.hpp"
+
+#include "linux/routing/link/link.hpp"
+
+#include "linux/routing/queueing/fq_codel.hpp"
+#include "linux/routing/queueing/htb.hpp"
+#include "linux/routing/queueing/ingress.hpp"
+#include "linux/routing/queueing/statistics.hpp"
+
+using namespace process;
+
+using namespace routing;
+using namespace routing::filter;
+using namespace routing::queueing;
+
+using std::endl;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+
+static const string TEST_VETH_LINK = "veth-test";
+static const string TEST_PEER_LINK = "veth-peer";
+
+
+class RoutingTest : public ::testing::Test
+{
+protected:
+ virtual void SetUp()
+ {
+ ASSERT_SOME(routing::check())
+ << "-------------------------------------------------------------\n"
+ << "We cannot run any routing tests because either your libnl\n"
+ << "library or kernel is not new enough. You can either upgrade,\n"
+ << "or disable this test case\n"
+ << "-------------------------------------------------------------";
+ }
+};
+
+
+// Tests that require setting up virtual ethernet on host.
+class RoutingVethTest : public RoutingTest
+{
+protected:
+ virtual void SetUp()
+ {
+ RoutingTest::SetUp();
+
+ // Clean up the test links, in case it wasn't cleaned up properly
+ // from previous tests.
+ link::remove(TEST_VETH_LINK);
+
+ ASSERT_SOME_FALSE(link::exists(TEST_VETH_LINK));
+ ASSERT_SOME_FALSE(link::exists(TEST_PEER_LINK));
+ }
+
+ virtual void TearDown()
+ {
+ link::remove(TEST_VETH_LINK);
+ }
+};
+
+
+TEST_F(RoutingTest, PortRange)
+{
+ Try<ip::PortRange> ports = ip::PortRange::fromBeginEnd(1, 0);
+ EXPECT_ERROR(ports);
+
+ ports = ip::PortRange::fromBeginEnd(4, 11);
+ EXPECT_ERROR(ports);
+
+ ports = ip::PortRange::fromBeginEnd(4, 7);
+ ASSERT_SOME(ports);
+ EXPECT_EQ(4u, ports.get().begin());
+ EXPECT_EQ(7u, ports.get().end());
+ EXPECT_EQ(0xfffc, ports.get().mask());
+ EXPECT_EQ("[4,7]", stringify(ports.get()));
+
+ ports = ip::PortRange::fromBeginEnd(10, 10);
+ ASSERT_SOME(ports);
+ EXPECT_EQ(10u, ports.get().begin());
+ EXPECT_EQ(10u, ports.get().end());
+ EXPECT_EQ(0xffff, ports.get().mask());
+ EXPECT_EQ("[10,10]", stringify(ports.get()));
+
+ ports = ip::PortRange::fromBeginMask(20, 0xffff);
+ ASSERT_SOME(ports);
+ EXPECT_EQ(20u, ports.get().begin());
+ EXPECT_EQ(20u, ports.get().end());
+ EXPECT_EQ(0xffff, ports.get().mask());
+ EXPECT_EQ("[20,20]", stringify(ports.get()));
+
+ ports = ip::PortRange::fromBeginMask(1024, 0xfff8);
+ ASSERT_SOME(ports);
+ EXPECT_EQ(1024u, ports.get().begin());
+ EXPECT_EQ(1031u, ports.get().end());
+ EXPECT_EQ(0xfff8, ports.get().mask());
+ EXPECT_EQ("[1024,1031]", stringify(ports.get()));
+}
+
+
+TEST_F(RoutingTest, RouteTable)
+{
+ Try<vector<route::Rule> > table = route::table();
+ EXPECT_SOME(table);
+
+ Result<net::IP> gateway = route::defaultGateway();
+ EXPECT_FALSE(gateway.isError());
+}
+
+
+TEST_F(RoutingTest, LinkIndex)
+{
+ Try<set<string> > links = net::links();
+ ASSERT_SOME(links);
+
+ foreach (const string& link, links.get()) {
+ EXPECT_SOME_NE(0, link::index(link));
+ }
+
+ EXPECT_NONE(link::index("not-exist"));
+}
+
+
+TEST_F(RoutingTest, LinkName)
+{
+ Try<set<string> > links = net::links();
+ ASSERT_SOME(links);
+
+ foreach (const string& link, links.get()) {
+ EXPECT_SOME_NE(0, link::index(link));
+ EXPECT_SOME_EQ(link, link::name(link::index(link).get()));
+ }
+}
+
+
+TEST_F(RoutingTest, LinkStatistics)
+{
+ Try<set<string> > links = net::links();
+ ASSERT_SOME(links);
+
+ foreach (const string& link, links.get()) {
+ Result<hashmap<string, uint64_t> > statistics = link::statistics(link);
+
+ ASSERT_SOME(statistics);
+ EXPECT_TRUE(statistics.get().contains("rx_packets"));
+ EXPECT_TRUE(statistics.get().contains("rx_bytes"));
+ EXPECT_TRUE(statistics.get().contains("tx_packets"));
+ EXPECT_TRUE(statistics.get().contains("tx_bytes"));
+ }
+
+ EXPECT_NONE(link::statistics("not-exist"));
+}
+
+
+TEST_F(RoutingTest, LinkExists)
+{
+ Try<set<string> > links = net::links();
+ ASSERT_SOME(links);
+
+ foreach (const string& link, links.get()) {
+ EXPECT_SOME_TRUE(link::exists(link));
+ }
+
+ EXPECT_SOME_FALSE(link::exists("not-exist"));
+}
+
+
+TEST_F(RoutingTest, Eth0)
+{
+ Result<string> eth0 = link::eth0();
+ EXPECT_FALSE(eth0.isError());
+
+ if (eth0.isSome()) {
+ ASSERT_SOME_TRUE(link::exists(eth0.get()));
+ }
+}
+
+
+TEST_F(RoutingTest, Lo)
+{
+ Result<string> lo = link::lo();
+ EXPECT_FALSE(lo.isError());
+
+ if (lo.isSome()) {
+ ASSERT_SOME_TRUE(link::exists(lo.get()));
+ }
+}
+
+
+TEST_F(RoutingTest, INETSockets)
+{
+ Try<vector<diagnosis::socket::Info> > infos =
+ diagnosis::socket::infos(AF_INET, diagnosis::socket::state::ALL);
+
+ EXPECT_SOME(infos);
+
+ foreach (const diagnosis::socket::Info& info, infos.get()) {
+ // Both source and destination IPs should be present since
+ // 'AF_INET' is asked for.
+ EXPECT_SOME(info.sourceIP);
+ EXPECT_SOME(info.destinationIP);
+ }
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkCreate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ EXPECT_SOME_NE(0, link::index(TEST_VETH_LINK));
+ EXPECT_SOME_NE(0, link::index(TEST_PEER_LINK));
+
+ // Test the case where the veth (with the same name) already exists.
+ EXPECT_SOME_FALSE(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkRemove)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::remove(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(link::remove(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(link::remove(TEST_PEER_LINK));
+}
+
+
+// An old glibc might not have this symbol.
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000
+#endif
+
+
+// Entry point of the child process (used in clone()).
+static int child(void*)
+{
+ // Wait to be killed.
+ while (true) {
+ sleep(1);
+ }
+
+ // Should not reach here.
+ ABORT("Child process should not reach here");
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkCreatePid)
+{
+ // Stack used in the child process.
+ unsigned long long stack[32];
+
+ pid_t pid = ::clone(child, &stack[31], CLONE_NEWNET | SIGCHLD, NULL);
+ ASSERT_NE(-1, pid);
+
+ // In parent process.
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, pid));
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+
+ // The peer should not exist in parent network namespace.
+ EXPECT_SOME_FALSE(link::exists(TEST_PEER_LINK));
+
+ // TODO(jieyu): Enter the child network namespace and make sure that
+ // the TEST_PEER_LINK is there.
+
+ EXPECT_SOME_NE(0, link::index(TEST_VETH_LINK));
+
+ // Kill the child process.
+ ASSERT_NE(-1, kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ int status;
+ EXPECT_NE(-1, waitpid((pid_t) -1, &status, 0));
+ ASSERT_TRUE(WIFSIGNALED(status));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkWait)
+{
+ AWAIT_READY(link::removed(TEST_VETH_LINK));
+
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ Future<Nothing> removed = link::removed(TEST_VETH_LINK);
+ EXPECT_TRUE(removed.isPending());
+
+ ASSERT_SOME_TRUE(link::remove(TEST_VETH_LINK));
+ AWAIT_READY(removed);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkSetUp)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ EXPECT_SOME_FALSE(link::isUp(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::setUp(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::isUp(TEST_VETH_LINK));
+
+ EXPECT_SOME_FALSE(link::isUp(TEST_PEER_LINK));
+ EXPECT_SOME_TRUE(link::setUp(TEST_PEER_LINK));
+ EXPECT_SOME_TRUE(link::isUp(TEST_PEER_LINK));
+
+ EXPECT_NONE(link::isUp("non-exist"));
+ EXPECT_SOME_FALSE(link::setUp("non-exist"));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkSetMAC)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ uint8_t bytes[6] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc};
+
+ EXPECT_SOME_TRUE(link::setMAC(TEST_VETH_LINK, net::MAC(bytes)));
+ EXPECT_SOME_TRUE(link::setMAC(TEST_PEER_LINK, net::MAC(bytes)));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+
+ ASSERT_SOME(mac);
+ EXPECT_EQ(mac.get(), net::MAC(bytes));
+
+ mac = net::mac(TEST_PEER_LINK);
+
+ ASSERT_SOME(mac);
+ EXPECT_EQ(mac.get(), net::MAC(bytes));
+
+ EXPECT_SOME_FALSE(link::setMAC("non-exist", net::MAC(bytes)));
+
+ // Kernel will reject a multicast MAC address.
+ uint8_t multicast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+ EXPECT_ERROR(link::setMAC(TEST_VETH_LINK, net::MAC(multicast)));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_LinkMTU)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ EXPECT_SOME_TRUE(link::setMTU(TEST_VETH_LINK, 10000));
+
+ Result<unsigned int> mtu = link::mtu(TEST_VETH_LINK);
+ ASSERT_SOME(mtu);
+ EXPECT_EQ(10000u, mtu.get());
+
+ EXPECT_NONE(link::mtu("not-exist"));
+ EXPECT_SOME_FALSE(link::setMTU("not-exist", 1500));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IngressQdisc)
+{
+ // Test for a qdisc on a nonexistent interface should fail.
+ EXPECT_SOME_FALSE(ingress::exists("noSuchInterface"));
+
+ EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ // Interface exists but does not have an ingress qdisc.
+ EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
+
+ // Interfaces without qdisc established no data.
+ EXPECT_NONE(ingress::statistics(TEST_VETH_LINK));
+ EXPECT_NONE(ingress::statistics(TEST_PEER_LINK));
+
+ // Try to create an ingress qdisc on a nonexistent interface.
+ EXPECT_ERROR(ingress::create("noSuchInterface"));
+
+ // Create an ingress qdisc on an existing interface.
+ EXPECT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ // Interface exists and has an ingress qdisc.
+ EXPECT_SOME_TRUE(ingress::exists(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
+
+ // Interfaces which exist return at least the core statisitcs.
+ Result<hashmap<string, uint64_t>> stats = ingress::statistics(TEST_VETH_LINK);
+ ASSERT_SOME(stats);
+ EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
+ EXPECT_TRUE(stats.get().contains(statistics::BYTES));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
+ EXPECT_TRUE(stats.get().contains(statistics::QLEN));
+ EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
+ EXPECT_TRUE(stats.get().contains(statistics::DROPS));
+ EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
+ EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
+
+ // Interface without qdisc returns no data.
+ EXPECT_NONE(ingress::statistics(TEST_PEER_LINK));
+
+ // Try to create a second ingress qdisc on an existing interface.
+ EXPECT_SOME_FALSE(ingress::create(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(ingress::exists(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
+
+ // Remove the ingress qdisc.
+ EXPECT_SOME_TRUE(ingress::remove(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
+
+ // Try to remove it from a nonexistent interface.
+ EXPECT_SOME_FALSE(ingress::remove("noSuchInterface"));
+
+ // Remove the ingress qdisc when it does not exist.
+ EXPECT_SOME_FALSE(ingress::remove(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_VETH_LINK));
+ EXPECT_SOME_FALSE(ingress::exists(TEST_PEER_LINK));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_HTBQdisc)
+{
+ // Test for a qdisc on a nonexistent interface should fail.
+ EXPECT_SOME_FALSE(htb::exists("noSuchInterface", EGRESS_ROOT));
+
+ EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ // This test uses a common handle throughout
+ const Handle handle = Handle(1, 0);
+
+ // Interface exists but does not have an htb qdisc.
+ EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Interfaces without qdisc established no data.
+ EXPECT_NONE(htb::statistics(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_NONE(htb::statistics(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create an htb qdisc on a nonexistent interface.
+ EXPECT_ERROR(htb::create("noSuchInterface", EGRESS_ROOT, handle));
+
+ // Create an htb qdisc on an existing interface.
+ EXPECT_SOME_TRUE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
+
+ // Interface exists and has an htb qdisc.
+ EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Interfaces which exist return at least the core statisitcs.
+ Result<hashmap<string, uint64_t>> stats =
+ htb::statistics(TEST_VETH_LINK, EGRESS_ROOT);
+ ASSERT_SOME(stats);
+ EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
+ EXPECT_TRUE(stats.get().contains(statistics::BYTES));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
+ EXPECT_TRUE(stats.get().contains(statistics::QLEN));
+ EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
+ EXPECT_TRUE(stats.get().contains(statistics::DROPS));
+ EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
+ EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
+
+ // Interface without htb qdisc returns no data.
+ EXPECT_NONE(htb::statistics(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create a second htb qdisc on an existing interface.
+ EXPECT_SOME_FALSE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
+ EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Remove the htb qdisc.
+ EXPECT_SOME_TRUE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to remove it from a nonexistent interface.
+ EXPECT_SOME_FALSE(htb::remove("noSuchInterface", EGRESS_ROOT));
+
+ // Remove the htb qdisc when it does not exist.
+ EXPECT_SOME_FALSE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create an htb qdisc on a nonexistent interface and
+ // default handle.
+ EXPECT_ERROR(htb::create("noSuchInterface", EGRESS_ROOT, None()));
+
+ // Create an htb qdisc on an existing interface.
+ EXPECT_SOME_TRUE(htb::create(TEST_VETH_LINK, EGRESS_ROOT, None()));
+
+ // Interface exists and has an htb qdisc.
+ EXPECT_SOME_TRUE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Remove the htb qdisc.
+ EXPECT_SOME_TRUE(htb::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(htb::exists(TEST_PEER_LINK, EGRESS_ROOT));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_FqCodeQdisc)
+{
+ // Test for a qdisc on a nonexistent interface should fail.
+ EXPECT_SOME_FALSE(fq_codel::exists("noSuchInterface", EGRESS_ROOT));
+
+ EXPECT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ // This test uses a common handle throughout
+ const Handle handle = Handle(1, 0);
+
+ // Interface exists but does not have an fq_codel qdisc.
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Interfaces without qdisc established no data.
+ EXPECT_NONE(fq_codel::statistics(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_NONE(fq_codel::statistics(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create an fq_codel qdisc on a nonexistent interface.
+ EXPECT_ERROR(fq_codel::create("noSuchInterface", EGRESS_ROOT, handle));
+
+ // Create an fq_codel qdisc on an existing interface.
+ EXPECT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
+
+ // Interface exists and has an fq_codel qdisc.
+ EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Interfaces which exist return at least the core statisitcs.
+ Result<hashmap<string, uint64_t>> stats =
+ fq_codel::statistics(TEST_VETH_LINK, EGRESS_ROOT);
+ ASSERT_SOME(stats);
+ EXPECT_TRUE(stats.get().contains(statistics::PACKETS));
+ EXPECT_TRUE(stats.get().contains(statistics::BYTES));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_BPS));
+ EXPECT_TRUE(stats.get().contains(statistics::RATE_PPS));
+ EXPECT_TRUE(stats.get().contains(statistics::QLEN));
+ EXPECT_TRUE(stats.get().contains(statistics::BACKLOG));
+ EXPECT_TRUE(stats.get().contains(statistics::DROPS));
+ EXPECT_TRUE(stats.get().contains(statistics::REQUEUES));
+ EXPECT_TRUE(stats.get().contains(statistics::OVERLIMITS));
+
+ // Interface without fq_codel qdisc returns no data.
+ EXPECT_NONE(fq_codel::statistics(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create a second fq_codel qdisc on an existing interface.
+ EXPECT_SOME_FALSE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
+ EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Remove the fq_codel qdisc.
+ EXPECT_SOME_TRUE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to remove it from a nonexistent interface.
+ EXPECT_SOME_FALSE(fq_codel::remove("noSuchInterface", EGRESS_ROOT));
+
+ // Remove the fq_codel qdisc when it does not exist.
+ EXPECT_SOME_FALSE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Try to create an fq_codel qdisc on a nonexistent interface and
+ // default handle.
+ EXPECT_ERROR(fq_codel::create("noSuchInterface", EGRESS_ROOT, None()));
+
+ // Create an fq_codel qdisc on an existing interface.
+ EXPECT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, None()));
+
+ // Interface exists and has an fq_codel qdisc.
+ EXPECT_SOME_TRUE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+
+ // Remove the fq_codel qdisc.
+ EXPECT_SOME_TRUE(fq_codel::remove(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_VETH_LINK, EGRESS_ROOT));
+ EXPECT_SOME_FALSE(fq_codel::exists(TEST_PEER_LINK, EGRESS_ROOT));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_FqCodelClassifier)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ const Handle handle = Handle(1, 0);
+ ASSERT_SOME_TRUE(fq_codel::create(TEST_VETH_LINK, EGRESS_ROOT, handle));
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ handle,
+ ETH_P_ALL,
+ None(),
+ Handle(handle, 0)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, handle, ETH_P_ALL));
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ handle,
+ ETH_P_ARP,
+ None(),
+ Handle(handle, 0)));
+
+ // There is a kernel bug which could cause this test fail. Please
+ // make sure your kernel, if newer than 3.14, has commit:
+ // b057df24a7536cce6c372efe9d0e3d1558afedf4
+ // (https://git.kernel.org/cgit/linux/kernel/git/davem/net.git).
+ // Please fix your kernel if you see this failure.
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, handle, ETH_P_ARP));
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ handle,
+ icmp::Classifier(None()),
+ None(),
+ Handle(handle, 0)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ handle,
+ icmp::Classifier(None())));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+ ASSERT_SOME(sourcePorts);
+
+ Try<ip::PortRange> destinationPorts =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+ ASSERT_SOME(destinationPorts);
+
+ ip::Classifier classifier =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts.get(),
+ destinationPorts.get());
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ handle,
+ classifier,
+ None(),
+ Handle(handle, 1)));
+
+ EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, handle, classifier));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ARPFilterCreate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ARPFilterCreateDuplicated)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ set<string> links;
+ links.insert(TEST_PEER_LINK);
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ None(),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+
+ EXPECT_SOME_FALSE(basic::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ARPFilterRemove)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ set<string> links;
+ links.insert(TEST_PEER_LINK);
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ None(),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+ EXPECT_SOME_TRUE(basic::remove(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+ EXPECT_SOME_FALSE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ARPFilterUpdate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ set<string> links;
+ links.insert(TEST_PEER_LINK);
+
+ EXPECT_SOME_FALSE(basic::update(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(basic::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+
+ EXPECT_SOME_TRUE(basic::update(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ETH_P_ARP,
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(basic::exists(TEST_VETH_LINK, ingress::HANDLE, ETH_P_ARP));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ICMPFilterCreate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip),
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip)));
+
+ Result<vector<icmp::Classifier> > classifiers =
+ icmp::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ ASSERT_EQ(1u, classifiers.get().size());
+ EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ICMPFilterCreateDuplicated)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ set<string> links;
+ links.insert(TEST_PEER_LINK);
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ None(),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+
+ EXPECT_SOME_FALSE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ None(),
+ action::Mirror(links)));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ICMPFilterCreateMultiple)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ net::IP ip1 = net::IP(0x01020304); // 1.2.3.4
+ net::IP ip2 = net::IP(0x05060708); // 5.6.7.8
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip1),
+ Priority(1, 1),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip2),
+ Priority(1, 2),
+ action::Redirect(TEST_PEER_LINK)));
+
+ Result<vector<icmp::Classifier> > classifiers =
+ icmp::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ ASSERT_EQ(2u, classifiers.get().size());
+ EXPECT_SOME_EQ(ip1, classifiers.get().front().destinationIP);
+ EXPECT_SOME_EQ(ip2, classifiers.get().back().destinationIP);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ICMPFilterRemove)
+{
+ ASSERT_SOME(link::create(
+ TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+
+ EXPECT_SOME_TRUE(icmp::remove(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+
+ EXPECT_SOME_FALSE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_ICMPFilterUpdate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ set<string> links;
+ links.insert(TEST_PEER_LINK);
+
+ EXPECT_SOME_FALSE(icmp::update(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(icmp::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+
+ EXPECT_SOME_FALSE(icmp::update(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(icmp::update(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None()),
+ action::Mirror(links)));
+
+ EXPECT_SOME_TRUE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(None())));
+
+ EXPECT_SOME_FALSE(icmp::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ icmp::Classifier(ip)));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IPFilterCreate)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+
+ ASSERT_SOME(sourcePorts);
+
+ Try<ip::PortRange> destinationPorts =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+
+ ASSERT_SOME(destinationPorts);
+
+ ip::Classifier classifier =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts.get(),
+ destinationPorts.get());
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier));
+
+ Result<vector<ip::Classifier> > classifiers =
+ ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ ASSERT_EQ(1u, classifiers.get().size());
+ EXPECT_SOME_EQ(mac.get(), classifiers.get().front().destinationMAC);
+ EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
+
+ EXPECT_SOME_EQ(
+ sourcePorts.get(),
+ classifiers.get().front().sourcePorts);
+
+ EXPECT_SOME_EQ(
+ destinationPorts.get(),
+ classifiers.get().front().destinationPorts);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IPFilterCreate2)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ net::IP ip(0x12345678);
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ip::Classifier(None(), ip, None(), None()),
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::exists(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ ip::Classifier(None(), ip, None(), None())));
+
+ Result<vector<ip::Classifier> > classifiers =
+ ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ ASSERT_EQ(1u, classifiers.get().size());
+ EXPECT_NONE(classifiers.get().front().destinationMAC);
+ EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
+ EXPECT_NONE(classifiers.get().front().sourcePorts);
+ EXPECT_NONE(classifiers.get().front().destinationPorts);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IPFilterCreateDuplicated)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+
+ ASSERT_SOME(sourcePorts);
+
+ Try<ip::PortRange> destinationPorts =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+
+ ASSERT_SOME(destinationPorts);
+
+ ip::Classifier classifier =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts.get(),
+ destinationPorts.get());
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier));
+
+ EXPECT_SOME_FALSE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IPFilterCreateMultiple)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts1 =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+
+ ASSERT_SOME(sourcePorts1);
+
+ Try<ip::PortRange> destinationPorts1 =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+
+ ASSERT_SOME(destinationPorts1);
+
+ Try<ip::PortRange> sourcePorts2 =
+ ip::PortRange::fromBeginEnd(3024, 3025);
+
+ ASSERT_SOME(sourcePorts2);
+
+ Try<ip::PortRange> destinationPorts2 =
+ ip::PortRange::fromBeginEnd(4000, 4003);
+
+ ASSERT_SOME(destinationPorts2);
+
+ ip::Classifier classifier1 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts1.get(),
+ destinationPorts1.get());
+
+ ip::Classifier classifier2 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts2.get(),
+ destinationPorts2.get());
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier1,
+ Priority(2, 1),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier2,
+ Priority(2, 2),
+ action::Redirect(TEST_PEER_LINK)));
+
+ Result<vector<ip::Classifier> > classifiers =
+ ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ ASSERT_EQ(2u, classifiers.get().size());
+
+ EXPECT_SOME_EQ(mac.get(), classifiers.get().front().destinationMAC);
+ EXPECT_SOME_EQ(ip, classifiers.get().front().destinationIP);
+
+ EXPECT_SOME_EQ(
+ sourcePorts1.get(),
+ classifiers.get().front().sourcePorts);
+
+ EXPECT_SOME_EQ(
+ destinationPorts1.get(),
+ classifiers.get().front().destinationPorts);
+
+ EXPECT_SOME_EQ(mac.get(), classifiers.get().back().destinationMAC);
+ EXPECT_SOME_EQ(ip, classifiers.get().back().destinationIP);
+
+ EXPECT_SOME_EQ(
+ sourcePorts2.get(),
+ classifiers.get().back().sourcePorts);
+
+ EXPECT_SOME_EQ(
+ destinationPorts2.get(),
+ classifiers.get().back().destinationPorts);
+}
+
+
+TEST_F(RoutingVethTest, ROOT_IPFilterRemove)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts1 =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+
+ ASSERT_SOME(sourcePorts1);
+
+ Try<ip::PortRange> destinationPorts1 =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+
+ ASSERT_SOME(destinationPorts1);
+
+ Try<ip::PortRange> sourcePorts2 =
+ ip::PortRange::fromBeginEnd(3024, 3025);
+
+ ASSERT_SOME(sourcePorts2);
+
+ Try<ip::PortRange> destinationPorts2 =
+ ip::PortRange::fromBeginEnd(4000, 4003);
+
+ ASSERT_SOME(destinationPorts2);
+
+ ip::Classifier classifier1 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts1.get(),
+ destinationPorts1.get());
+
+ ip::Classifier classifier2 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts2.get(),
+ destinationPorts2.get());
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier1,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier2,
+ None(),
+ action::Redirect(TEST_PEER_LINK)));
+
+ EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier1));
+ EXPECT_SOME_FALSE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier1));
+
+ EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier2));
+ EXPECT_SOME_FALSE(ip::exists(TEST_VETH_LINK, ingress::HANDLE, classifier2));
+
+ Result<vector<ip::Classifier> > classifiers =
+ ip::classifiers(TEST_VETH_LINK, ingress::HANDLE);
+
+ ASSERT_SOME(classifiers);
+ EXPECT_EQ(0u, classifiers.get().size());
+}
+
+
+// Test the workaround introduced for MESOS-1617.
+TEST_F(RoutingVethTest, ROOT_HandleGeneration)
+{
+ ASSERT_SOME(link::create(TEST_VETH_LINK, TEST_PEER_LINK, None()));
+
+ EXPECT_SOME_TRUE(link::exists(TEST_VETH_LINK));
+ EXPECT_SOME_TRUE(link::exists(TEST_PEER_LINK));
+
+ ASSERT_SOME_TRUE(ingress::create(TEST_VETH_LINK));
+
+ Result<net::MAC> mac = net::mac(TEST_VETH_LINK);
+ ASSERT_SOME(mac);
+
+ net::IP ip = net::IP(0x01020304); // 1.2.3.4
+
+ Try<ip::PortRange> sourcePorts1 =
+ ip::PortRange::fromBeginEnd(1024, 1027);
+
+ ASSERT_SOME(sourcePorts1);
+
+ Try<ip::PortRange> destinationPorts1 =
+ ip::PortRange::fromBeginEnd(2000, 2000);
+
+ ASSERT_SOME(destinationPorts1);
+
+ Try<ip::PortRange> sourcePorts2 =
+ ip::PortRange::fromBeginEnd(3024, 3025);
+
+ ASSERT_SOME(sourcePorts2);
+
+ Try<ip::PortRange> destinationPorts2 =
+ ip::PortRange::fromBeginEnd(4000, 4003);
+
+ ASSERT_SOME(destinationPorts2);
+
+ ip::Classifier classifier1 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts1.get(),
+ destinationPorts1.get());
+
+ ip::Classifier classifier2 =
+ ip::Classifier(
+ mac.get(),
+ ip,
+ sourcePorts2.get(),
+ destinationPorts2.get());
+
+ // Use handle 800:00:fff for the first filter.
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier1,
+ Priority(2, 1),
+ U32Handle(0x800, 0x0, 0xfff),
+ action::Redirect(TEST_PEER_LINK)));
+
+ // With the workaround, this filter should be assigned a handle
+ // different than 800:00:fff.
+ EXPECT_SOME_TRUE(ip::create(
+ TEST_VETH_LINK,
+ ingress::HANDLE,
+ classifier2,
+ Priority(2, 1),
+ action::Redirect(TEST_PEER_LINK)));
+
+ // Try to remove the second filter. If we don't have the workaround,
+ // removing the second filter will return false since the kernel
+ // will find the handle matches the first filter.
+ EXPECT_SOME_TRUE(ip::remove(TEST_VETH_LINK, ingress::HANDLE, classifier2));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/sched_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/sched_tests.cpp b/src/tests/containerizer/sched_tests.cpp
new file mode 100644
index 0000000..00723d0
--- /dev/null
+++ b/src/tests/containerizer/sched_tests.cpp
@@ -0,0 +1,96 @@
+/**
+ * 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 "linux/sched.hpp"
+
+#include <process/gtest.hpp>
+#include <process/reap.hpp>
+
+#include <gtest/gtest.h>
+
+#include <stout/gtest.hpp>
+
+using sched::Policy;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// TODO(idownes): Test the priority and preemption behavior for
+// running competing SCHED_OTHER and SCHED_IDLE tasks.
+
+TEST(SchedTest, ROOT_PolicySelf)
+{
+ Try<Policy> original = sched::policy::get();
+ ASSERT_SOME(original);
+
+ Policy different = (original.get() == Policy::OTHER ? Policy::IDLE
+ : Policy::OTHER);
+
+ // Change our own scheduling policy.
+ EXPECT_SOME(sched::policy::set(different));
+ EXPECT_SOME_EQ(different, sched::policy::get());
+
+ // Change it back.
+ EXPECT_SOME(sched::policy::set(original.get()));
+ EXPECT_SOME_EQ(original.get(), sched::policy::get());
+}
+
+
+// Change the scheduling policy of a different process (our child).
+TEST(SchedTest, ROOT_PolicyChild)
+{
+ Try<Policy> original = sched::policy::get();
+ ASSERT_SOME(original);
+
+ Policy different = (original.get() == Policy::OTHER ? Policy::IDLE
+ : Policy::OTHER);
+
+ pid_t pid = ::fork();
+ ASSERT_NE(-1, pid);
+
+ if (pid == 0) {
+ // Child.
+ sleep(10);
+
+ ABORT("Child process should not reach here");
+ }
+
+ // Continue in parent.
+ // Check the child has inherited our policy.
+ EXPECT_SOME_EQ(original.get(), sched::policy::get(pid));
+
+ // Check we can change the child's policy.
+ EXPECT_SOME(sched::policy::set(different, pid));
+ EXPECT_SOME_EQ(different, sched::policy::get(pid));
+
+ process::Future<Option<int>> status = process::reap(pid);
+
+ // Kill the child process.
+ ASSERT_NE(-1, ::kill(pid, SIGKILL));
+
+ // Wait for the child process.
+ AWAIT_READY(status);
+ ASSERT_SOME(status.get());
+ EXPECT_TRUE(WIFSIGNALED(status.get().get()));
+ EXPECT_EQ(SIGKILL, WTERMSIG(status.get().get()));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/setns_test_helper.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/setns_test_helper.cpp b/src/tests/containerizer/setns_test_helper.cpp
new file mode 100644
index 0000000..ec68b08
--- /dev/null
+++ b/src/tests/containerizer/setns_test_helper.cpp
@@ -0,0 +1,63 @@
+/**
+ * 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 <set>
+#include <string>
+
+#include <stout/foreach.hpp>
+#include <stout/subcommand.hpp>
+#include <stout/try.hpp>
+
+#include "linux/ns.hpp"
+
+#include "tests/containerizer/setns_test_helper.hpp"
+
+using std::set;
+using std::string;
+
+const char SetnsTestHelper::NAME[] = "SetnsTestHelper";
+
+int SetnsTestHelper::execute()
+{
+ // Get all the available namespaces.
+ set<string> namespaces = ns::namespaces();
+
+ // Note: /proc has not been remounted so we can look up pid 1's
+ // namespaces, even if we're in a separate pid namespace.
+ foreach (const string& ns, namespaces) {
+ if (ns == "pid") {
+ // ns::setns() does not (currently) support pid namespaces so
+ // this should return an error.
+ Try<Nothing> setns = ns::setns(1, ns);
+ if (!setns.isError()) {
+ return 1;
+ }
+ } else if (ns == "user") {
+ // ns::setns() will also fail with user namespaces, so we skip
+ // for now. See MESOS-3083.
+ continue;
+ } else {
+ Try<Nothing> setns = ns::setns(1, ns);
+ if (!setns.isSome()) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/setns_test_helper.hpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/setns_test_helper.hpp b/src/tests/containerizer/setns_test_helper.hpp
new file mode 100644
index 0000000..51d6378
--- /dev/null
+++ b/src/tests/containerizer/setns_test_helper.hpp
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __SETNS_TEST_HELPER_HPP__
+#define __SETNS_TEST_HELPER_HPP__
+
+#include <stout/subcommand.hpp>
+
+class SetnsTestHelper : public Subcommand
+{
+public:
+ static const char NAME[];
+
+ SetnsTestHelper() : Subcommand(NAME) {}
+
+protected:
+ virtual int execute();
+};
+
+#endif // __SETNS_TEST_HELPER_HPP__
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer/setns_test_helper_main.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/setns_test_helper_main.cpp b/src/tests/containerizer/setns_test_helper_main.cpp
new file mode 100644
index 0000000..c8e270a
--- /dev/null
+++ b/src/tests/containerizer/setns_test_helper_main.cpp
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+#include <stout/subcommand.hpp>
+
+#include "tests/containerizer/setns_test_helper.hpp"
+
+int main(int argc, char** argv)
+{
+ return Subcommand::dispatch(
+ None(),
+ argc,
+ argv,
+ new SetnsTestHelper());
+}
http://git-wip-us.apache.org/repos/asf/mesos/blob/96351372/src/tests/containerizer_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer_tests.cpp b/src/tests/containerizer_tests.cpp
deleted file mode 100644
index 9508613..0000000
--- a/src/tests/containerizer_tests.cpp
+++ /dev/null
@@ -1,731 +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 <list>
-#include <map>
-#include <string>
-#include <vector>
-
-#include <gmock/gmock.h>
-
-#include <mesos/mesos.hpp>
-
-#include <mesos/slave/isolator.hpp>
-
-#include <process/future.hpp>
-#include <process/owned.hpp>
-
-#include <stout/strings.hpp>
-
-#include "slave/flags.hpp"
-
-#include "slave/containerizer/fetcher.hpp"
-#include "slave/containerizer/launcher.hpp"
-
-#include "slave/containerizer/mesos/containerizer.hpp"
-
-#include "tests/flags.hpp"
-#include "tests/isolator.hpp"
-#include "tests/launcher.hpp"
-#include "tests/mesos.hpp"
-#include "tests/utils.hpp"
-
-using namespace process;
-
-using mesos::internal::master::Master;
-
-using mesos::internal::slave::Fetcher;
-using mesos::internal::slave::Launcher;
-using mesos::internal::slave::MesosContainerizer;
-using mesos::internal::slave::MesosContainerizerProcess;
-using mesos::internal::slave::PosixLauncher;
-using mesos::internal::slave::Provisioner;
-using mesos::internal::slave::Slave;
-
-using mesos::internal::slave::state::ExecutorState;
-using mesos::internal::slave::state::FrameworkState;
-using mesos::internal::slave::state::RunState;
-using mesos::internal::slave::state::SlaveState;
-
-using mesos::slave::ExecutorLimitation;
-using mesos::slave::ExecutorRunState;
-using mesos::slave::Isolator;
-using mesos::slave::IsolatorProcess;
-
-using std::list;
-using std::map;
-using std::string;
-using std::vector;
-
-using testing::_;
-using testing::DoAll;
-using testing::Invoke;
-using testing::Return;
-
-namespace mesos {
-namespace internal {
-namespace tests {
-
-class MesosContainerizerIsolatorPreparationTest :
- public TemporaryDirectoryTest
-{
-public:
- // Construct a MesosContainerizer with TestIsolator(s) which use the provided
- // 'prepare' command(s).
- Try<MesosContainerizer*> CreateContainerizer(
- Fetcher* fetcher,
- const vector<Option<CommandInfo>>& prepares)
- {
- vector<Owned<Isolator>> isolators;
-
- foreach (const Option<CommandInfo>& prepare, prepares) {
- Try<Isolator*> isolator = TestIsolatorProcess::create(prepare);
- if (isolator.isError()) {
- return Error(isolator.error());
- }
-
- isolators.push_back(Owned<Isolator>(isolator.get()));
- }
-
- slave::Flags flags;
- flags.launcher_dir = path::join(tests::flags.build_dir, "src");
-
- Try<Launcher*> launcher = PosixLauncher::create(flags);
- if (launcher.isError()) {
- return Error(launcher.error());
- }
-
- return new MesosContainerizer(
- flags,
- false,
- fetcher,
- Owned<Launcher>(launcher.get()),
- isolators,
- hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
- }
-
- Try<MesosContainerizer*> CreateContainerizer(
- Fetcher* fetcher,
- const Option<CommandInfo>& prepare)
- {
- vector<Option<CommandInfo>> prepares;
- prepares.push_back(prepare);
-
- return CreateContainerizer(fetcher, prepares);
- }
-};
-
-
-// The isolator has a prepare command that succeeds.
-TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptSucceeds)
-{
- string directory = os::getcwd(); // We're inside a temporary sandbox.
- string file = path::join(directory, "child.script.executed");
-
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer = CreateContainerizer(
- &fetcher,
- CREATE_COMMAND_INFO("touch " + file));
-
- CHECK_SOME(containerizer);
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- Future<bool> launch = containerizer.get()->launch(
- containerId,
- CREATE_EXECUTOR_INFO("executor", "exit 0"),
- directory,
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- // Wait until the launch completes.
- AWAIT_READY(launch);
-
- // Wait for the child (preparation script + executor) to complete.
- Future<containerizer::Termination> wait =
- containerizer.get()->wait(containerId);
-
- AWAIT_READY(wait);
-
- // Check the child exited correctly.
- EXPECT_TRUE(wait.get().has_status());
- EXPECT_EQ(0, wait.get().status());
-
- // Check the preparation script actually ran.
- EXPECT_TRUE(os::exists(file));
-
- // Destroy the container.
- containerizer.get()->destroy(containerId);
-
- delete containerizer.get();
-}
-
-
-// The isolator has a prepare command that fails.
-TEST_F(MesosContainerizerIsolatorPreparationTest, ScriptFails)
-{
- string directory = os::getcwd(); // We're inside a temporary sandbox.
- string file = path::join(directory, "child.script.executed");
-
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer = CreateContainerizer(
- &fetcher,
- CREATE_COMMAND_INFO("touch " + file + " && exit 1"));
-
- CHECK_SOME(containerizer);
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- Future<bool> launch = containerizer.get()->launch(
- containerId,
- CREATE_EXECUTOR_INFO("executor", "exit 0"),
- directory,
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- // Wait until the launch completes.
- AWAIT_READY(launch);
-
- // Wait for the child (preparation script + executor) to complete.
- Future<containerizer::Termination> wait =
- containerizer.get()->wait(containerId);
-
- AWAIT_READY(wait);
-
- // Check the child failed to exit correctly.
- EXPECT_TRUE(wait.get().has_status());
- EXPECT_NE(0, wait.get().status());
-
- // Check the preparation script actually ran.
- EXPECT_TRUE(os::exists(file));
-
- // Destroy the container.
- containerizer.get()->destroy(containerId);
-
- delete containerizer.get();
-}
-
-
-// There are two isolators, one with a prepare command that succeeds
-// and another that fails. The execution order is not defined but the
-// launch should fail from the failing prepare command.
-TEST_F(MesosContainerizerIsolatorPreparationTest, MultipleScripts)
-{
- string directory = os::getcwd(); // We're inside a temporary sandbox.
- string file1 = path::join(directory, "child.script.executed.1");
- string file2 = path::join(directory, "child.script.executed.2");
-
- vector<Option<CommandInfo>> prepares;
-
- // This isolator prepare command one will succeed if called first,
- // otherwise it won't get run.
- prepares.push_back(CREATE_COMMAND_INFO("touch " + file1 + " && exit 0"));
-
- // This will fail, either first or after the successful command.
- prepares.push_back(CREATE_COMMAND_INFO("touch " + file2 + " && exit 1"));
-
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer =
- CreateContainerizer(&fetcher, prepares);
-
- CHECK_SOME(containerizer);
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- Future<bool> launch = containerizer.get()->launch(
- containerId,
- CREATE_EXECUTOR_INFO("executor", "exit 0"),
- directory,
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- // Wait until the launch completes.
- AWAIT_READY(launch);
-
- // Wait for the child (preparation script(s) + executor) to complete.
- Future<containerizer::Termination> wait =
- containerizer.get()->wait(containerId);
- AWAIT_READY(wait);
-
- // Check the child failed to exit correctly.
- EXPECT_TRUE(wait.get().has_status());
- EXPECT_NE(0, wait.get().status());
-
- // Check the failing preparation script has actually ran.
- EXPECT_TRUE(os::exists(file2));
-
- // Destroy the container.
- containerizer.get()->destroy(containerId);
-
- delete containerizer.get();
-}
-
-
-class MesosContainerizerExecuteTest : public TemporaryDirectoryTest {};
-
-
-TEST_F(MesosContainerizerExecuteTest, IoRedirection)
-{
- string directory = os::getcwd(); // We're inside a temporary sandbox.
-
- slave::Flags flags;
- flags.launcher_dir = path::join(tests::flags.build_dir, "src");
-
- Fetcher fetcher;
-
- // Use local=false so std{err,out} are redirected to files.
- Try<MesosContainerizer*> containerizer =
- MesosContainerizer::create(flags, false, &fetcher);
-
- ASSERT_SOME(containerizer);
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- string errMsg = "this is stderr";
- string outMsg = "this is stdout";
- string command =
- "(echo '" + errMsg + "' 1>&2) && echo '" + outMsg + "'";
-
- Future<bool> launch = containerizer.get()->launch(
- containerId,
- CREATE_EXECUTOR_INFO("executor", command),
- directory,
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- // Wait for the launch to complete.
- AWAIT_READY(launch);
-
- // Wait on the container.
- Future<containerizer::Termination> wait =
- containerizer.get()->wait(containerId);
-
- AWAIT_READY(wait);
-
- // Check the executor exited correctly.
- EXPECT_TRUE(wait.get().has_status());
- EXPECT_EQ(0, wait.get().status());
-
- // Check that std{err, out} was redirected.
- // NOTE: Fetcher uses GLOG, which outputs extra information to
- // stderr.
- Try<string> stderr = os::read(path::join(directory, "stderr"));
- ASSERT_SOME(stderr);
- EXPECT_TRUE(strings::contains(stderr.get(), errMsg));
-
- EXPECT_SOME_EQ(outMsg + "\n", os::read(path::join(directory, "stdout")));
-
- delete containerizer.get();
-}
-
-
-class MesosContainerizerDestroyTest : public MesosTest {};
-
-
-class MockMesosContainerizerProcess : public MesosContainerizerProcess
-{
-public:
- MockMesosContainerizerProcess(
- const slave::Flags& flags,
- bool local,
- Fetcher* fetcher,
- const Owned<Launcher>& launcher,
- const vector<Owned<Isolator>>& isolators,
- const hashmap<ContainerInfo::Image::Type,
- Owned<Provisioner>>& provisioners)
- : MesosContainerizerProcess(
- flags,
- local,
- fetcher,
- launcher,
- isolators,
- provisioners)
- {
- // NOTE: See TestContainerizer::setup for why we use
- // 'EXPECT_CALL' and 'WillRepeatedly' here instead of
- // 'ON_CALL' and 'WillByDefault'.
- EXPECT_CALL(*this, exec(_, _))
- .WillRepeatedly(Invoke(this, &MockMesosContainerizerProcess::_exec));
- }
-
- MOCK_METHOD2(
- exec,
- Future<bool>(
- const ContainerID& containerId,
- int pipeWrite));
-
- Future<bool> _exec(
- const ContainerID& containerId,
- int pipeWrite)
- {
- return MesosContainerizerProcess::exec(
- containerId,
- pipeWrite);
- }
-};
-
-
-class MockIsolator : public mesos::slave::Isolator
-{
-public:
- MockIsolator()
- {
- EXPECT_CALL(*this, watch(_))
- .WillRepeatedly(Return(watchPromise.future()));
-
- EXPECT_CALL(*this, isolate(_, _))
- .WillRepeatedly(Return(Nothing()));
-
- EXPECT_CALL(*this, cleanup(_))
- .WillRepeatedly(Return(Nothing()));
-
- EXPECT_CALL(*this, prepare(_, _, _, _, _))
- .WillRepeatedly(Invoke(this, &MockIsolator::_prepare));
- }
-
- MOCK_METHOD2(
- recover,
- Future<Nothing>(
- const list<ExecutorRunState>&,
- const hashset<ContainerID>&));
-
- MOCK_METHOD5(
- prepare,
- Future<Option<CommandInfo>>(
- const ContainerID&,
- const ExecutorInfo&,
- const string&,
- const Option<string>&,
- const Option<string>&));
-
- virtual Future<Option<CommandInfo>> _prepare(
- const ContainerID& containerId,
- const ExecutorInfo& executorInfo,
- const string& directory,
- const Option<string>& rootfs,
- const Option<string>& user)
- {
- return None();
- }
-
- MOCK_METHOD2(
- isolate,
- Future<Nothing>(const ContainerID&, pid_t));
-
- MOCK_METHOD1(
- watch,
- Future<mesos::slave::ExecutorLimitation>(const ContainerID&));
-
- MOCK_METHOD2(
- update,
- Future<Nothing>(const ContainerID&, const Resources&));
-
- MOCK_METHOD1(
- usage,
- Future<ResourceStatistics>(const ContainerID&));
-
- MOCK_METHOD1(
- cleanup,
- Future<Nothing>(const ContainerID&));
-
- Promise<mesos::slave::ExecutorLimitation> watchPromise;
-};
-
-
-// Destroying a mesos containerizer while it is fetching should
-// complete without waiting for the fetching to finish.
-TEST_F(MesosContainerizerDestroyTest, DestroyWhileFetching)
-{
- slave::Flags flags = CreateSlaveFlags();
-
- Try<Launcher*> launcher = PosixLauncher::create(flags);
- ASSERT_SOME(launcher);
-
- Fetcher fetcher;
-
- MockMesosContainerizerProcess* process = new MockMesosContainerizerProcess(
- flags,
- true,
- &fetcher,
- Owned<Launcher>(launcher.get()),
- vector<Owned<Isolator>>(),
- hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
-
- Future<Nothing> exec;
- Promise<bool> promise;
-
- // Letting exec hang to simulate a long fetch.
- EXPECT_CALL(*process, exec(_, _))
- .WillOnce(DoAll(FutureSatisfy(&exec),
- Return(promise.future())));
-
- MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- TaskInfo taskInfo;
- CommandInfo commandInfo;
- taskInfo.mutable_command()->MergeFrom(commandInfo);
-
- containerizer.launch(
- containerId,
- taskInfo,
- CREATE_EXECUTOR_INFO("executor", "exit 0"),
- os::getcwd(),
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- Future<containerizer::Termination> wait = containerizer.wait(containerId);
-
- AWAIT_READY(exec);
-
- containerizer.destroy(containerId);
-
- // The container should still exit even if fetch didn't complete.
- AWAIT_READY(wait);
-}
-
-
-// Destroying a mesos containerizer while it is preparing should wait
-// until isolators are finished preparing before destroying.
-TEST_F(MesosContainerizerDestroyTest, DestroyWhilePreparing)
-{
- slave::Flags flags = CreateSlaveFlags();
-
- Try<Launcher*> launcher = PosixLauncher::create(flags);
- ASSERT_SOME(launcher);
-
- MockIsolator* isolator = new MockIsolator();
-
- Future<Nothing> prepare;
- Promise<Option<CommandInfo>> promise;
-
- // Simulate a long prepare from the isolator.
- EXPECT_CALL(*isolator, prepare(_, _, _, _, _))
- .WillOnce(DoAll(FutureSatisfy(&prepare),
- Return(promise.future())));
-
- Fetcher fetcher;
-
- MockMesosContainerizerProcess* process = new MockMesosContainerizerProcess(
- flags,
- true,
- &fetcher,
- Owned<Launcher>(launcher.get()),
- {Owned<Isolator>(isolator)},
- hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
-
- MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- TaskInfo taskInfo;
- CommandInfo commandInfo;
- taskInfo.mutable_command()->MergeFrom(commandInfo);
-
- containerizer.launch(
- containerId,
- taskInfo,
- CREATE_EXECUTOR_INFO("executor", "exit 0"),
- os::getcwd(),
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- Future<containerizer::Termination> wait = containerizer.wait(containerId);
-
- AWAIT_READY(prepare);
-
- containerizer.destroy(containerId);
-
- // The container should not exit until prepare is complete.
- ASSERT_TRUE(wait.isPending());
-
- // Need to help the compiler to disambiguate between overloads.
- Option<CommandInfo> option = commandInfo;
- promise.set(option);
-
- AWAIT_READY(wait);
-
- containerizer::Termination termination = wait.get();
-
- EXPECT_EQ(
- "Container destroyed while preparing isolators",
- termination.message());
-
- EXPECT_TRUE(termination.killed());
- EXPECT_FALSE(termination.has_status());
-}
-
-
-// This action destroys the container using the real launcher and
-// waits until the destroy is complete.
-ACTION_P(InvokeDestroyAndWait, launcher)
-{
- Future<Nothing> destroy = launcher->real->destroy(arg0);
- AWAIT_READY(destroy);
-}
-
-
-// This test verifies that when a container destruction fails the
-// 'container_destroy_errors' metric is updated.
-TEST_F(MesosContainerizerDestroyTest, LauncherDestroyFailure)
-{
- // Create a TestLauncher backed by PosixLauncher.
- slave::Flags flags = CreateSlaveFlags();
-
- Try<Launcher*> launcher_ = PosixLauncher::create(flags);
- ASSERT_SOME(launcher_);
-
- TestLauncher* launcher = new TestLauncher(Owned<Launcher>(launcher_.get()));
-
- Fetcher fetcher;
-
- MesosContainerizerProcess* process = new MesosContainerizerProcess(
- flags,
- true,
- &fetcher,
- Owned<Launcher>(launcher),
- vector<Owned<Isolator>>(),
- hashmap<ContainerInfo::Image::Type, Owned<Provisioner>>());
-
- MesosContainerizer containerizer((Owned<MesosContainerizerProcess>(process)));
-
- ContainerID containerId;
- containerId.set_value("test_container");
-
- TaskInfo taskInfo;
- CommandInfo commandInfo;
- taskInfo.mutable_command()->MergeFrom(commandInfo);
-
- // Destroy the container using the PosixLauncher but return a failed
- // future to the containerizer.
- EXPECT_CALL(*launcher, destroy(_))
- .WillOnce(DoAll(InvokeDestroyAndWait(launcher),
- Return(Failure("Destroy failure"))));
-
- Future<bool> launch = containerizer.launch(
- containerId,
- taskInfo,
- CREATE_EXECUTOR_INFO("executor", "sleep 1000"),
- os::getcwd(),
- None(),
- SlaveID(),
- PID<Slave>(),
- false);
-
- AWAIT_READY(launch);
-
- Future<containerizer::Termination> wait = containerizer.wait(containerId);
-
- containerizer.destroy(containerId);
-
- // The container destroy should fail.
- AWAIT_FAILED(wait);
-
- // 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();
-
- // Ensure that the metric is updated.
- JSON::Object metrics = Metrics();
- ASSERT_EQ(
- 1u,
- metrics.values.count("containerizer/mesos/container_destroy_errors"));
- ASSERT_EQ(
- 1u,
- metrics.values["containerizer/mesos/container_destroy_errors"]);
-}
-
-
-class MesosContainerizerRecoverTest : public MesosTest {};
-
-
-// This test checks that MesosContainerizer doesn't recover executors
-// that were started by another containerizer (e.g: Docker).
-TEST_F(MesosContainerizerRecoverTest, SkipRecoverNonMesosContainers)
-{
- slave::Flags flags = CreateSlaveFlags();
- Fetcher fetcher;
-
- Try<MesosContainerizer*> containerizer =
- MesosContainerizer::create(flags, true, &fetcher);
-
- ASSERT_SOME(containerizer);
-
- ExecutorID executorId;
- executorId.set_value(UUID::random().toString());
-
- ContainerID containerId;
- containerId.set_value(UUID::random().toString());
-
- ExecutorInfo executorInfo;
- executorInfo.mutable_container()->set_type(ContainerInfo::DOCKER);
-
- ExecutorState executorState;
- executorState.info = executorInfo;
- executorState.latest = containerId;
-
- RunState runState;
- runState.id = containerId;
- executorState.runs.put(containerId, runState);
-
- FrameworkState frameworkState;
- frameworkState.executors.put(executorId, executorState);
-
- SlaveState slaveState;
- FrameworkID frameworkId;
- frameworkId.set_value(UUID::random().toString());
- slaveState.frameworks.put(frameworkId, frameworkState);
-
- Future<Nothing> recover = containerizer.get()->recover(slaveState);
- AWAIT_READY(recover);
-
- Future<hashset<ContainerID>> containers = containerizer.get()->containers();
- AWAIT_READY(containers);
- EXPECT_EQ(0u, containers.get().size());
-
- delete containerizer.get();
-}
-
-} // namespace tests {
-} // namespace internal {
-} // namespace mesos {