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 {
