mesos-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ji...@apache.org
Subject [mesos] branch master updated: Added a CNI test for networking statistics.
Date Tue, 28 Aug 2018 20:25:57 GMT
This is an automated email from the ASF dual-hosted git repository.

jieyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git


The following commit(s) were added to refs/heads/master by this push:
     new 0a58ecd  Added a CNI test for networking statistics.
0a58ecd is described below

commit 0a58ecd86dfd025526c6a2f719df096ec8195c99
Author: Sergey Urbanovich <sergey.urbanovich@gmail.com>
AuthorDate: Tue Aug 28 12:15:00 2018 -0700

    Added a CNI test for networking statistics.
    
    This is a veth CNI plugin that is written in bash. It creates a veth
    virtual network pair, one end of the pair will be moved to container
    network namespace.
    
    The veth CNI plugin uses 203.0.113.0/24 subnet, it is reserved for
    documentation and examples [rfc5737]. The plugin can allocate up to
    128 veth pairs.
    
    Review: https://reviews.apache.org/r/68355/
---
 src/tests/containerizer/cni_isolator_tests.cpp | 200 ++++++++++++++++++++++++-
 src/tests/environment.cpp                      |  74 +++++++++
 2 files changed, 269 insertions(+), 5 deletions(-)

diff --git a/src/tests/containerizer/cni_isolator_tests.cpp b/src/tests/containerizer/cni_isolator_tests.cpp
index 63b109b..ed14bb1 100644
--- a/src/tests/containerizer/cni_isolator_tests.cpp
+++ b/src/tests/containerizer/cni_isolator_tests.cpp
@@ -196,31 +196,110 @@ public:
           "type": "mockPlugin"
         })~");
 
+    // This is a veth CNI plugin that is written in bash. It creates a
+    // veth virtual network pair, one end of the pair will be moved to
+    // container network namespace.
+    //
+    // The veth CNI plugin uses 203.0.113.0/24 subnet, it is reserved
+    // for documentation and examples [rfc5737]. The plugin can
+    // allocate up to 128 veth pairs.
+    string vethPlugin =
+        R"~(
+          #!/bin/sh
+          set -e
+          IP_ADDR="203.0.113.%s"
+
+          NETNS="mesos-test-veth-${PPID}"
+          mkdir -p /var/run/netns
+          cleanup() {
+            rm -f /var/run/netns/$NETNS
+          }
+          trap cleanup EXIT
+          ln -sf $CNI_NETNS /var/run/netns/$NETNS
+
+          case $CNI_COMMAND in
+          "ADD"*)
+            for idx in `seq 0 127`; do
+              VETH0="vmesos${idx}"
+              VETH1="vmesos${idx}ns"
+              ip link add name $VETH0 type veth peer name $VETH1 2> /dev/null || continue
+              VETH0_IP=$(printf $IP_ADDR $(($idx * 2)))
+              VETH1_IP=$(printf $IP_ADDR $(($idx * 2 + 1)))
+              ip addr add "${VETH0_IP}/31" dev $VETH0
+              ip link set $VETH0 up
+              ip link set $VETH1 netns $NETNS
+              ip netns exec $NETNS ip addr add "${VETH1_IP}/31" dev $VETH1
+              ip netns exec $NETNS ip link set $VETH1 name $CNI_IFNAME up
+              ip netns exec $NETNS ip route add default via $VETH0_IP dev $CNI_IFNAME
+              break
+            done
+            echo '{'
+            echo '  "cniVersion": "0.2.3",'
+            if [ -z "$VETH1_IP" ]; then
+              echo '  "code": 100,'
+              echo '  "msg": "Bad IP address"'
+              echo '}'
+              exit 100
+            else
+              echo '  "ip4": {'
+              echo '    "ip": "'$VETH1_IP'/31"'
+              echo '  }'
+              echo '}'
+            fi
+            ;;
+          "DEL"*)
+            # $VETH0 on host network namespace will be removed automatically.
+            # If the plugin can't destroy the veth pair, it will be destroyed
+            # with the container network namespace.
+            ip netns exec $NETNS ip link del $CNI_IFNAME || true
+            ;;
+          esac
+        )~";
+
+    ASSERT_SOME(setupPlugin(vethPlugin, "vethPlugin"));
+
+    // Generate the mock CNI config for veth CNI plugin.
+    ASSERT_SOME(os::mkdir(cniConfigDir));
+
+    result = os::write(
+        path::join(cniConfigDir, "vethConfig"),
+        R"~(
+        {
+          "name": "veth",
+          "type": "vethPlugin"
+        })~");
+
     ASSERT_SOME(result);
   }
 
   // Generate the mock CNI plugin based on the given script.
   Try<Nothing> setupMockPlugin(const string& pluginScript)
   {
+    return setupPlugin(pluginScript, "mockPlugin");
+  }
+
+  // Generate the CNI plugin based on the given script.
+  Try<Nothing> setupPlugin(const string& pluginScript, const string& pluginName)
+  {
     Try<Nothing> mkdir = os::mkdir(cniPluginDir);
     if (mkdir.isError()) {
       return Error("Failed to mkdir '" + cniPluginDir + "': " + mkdir.error());
     }
 
-    string mockPlugin = path::join(cniPluginDir, "mockPlugin");
+    string pluginPath = path::join(cniPluginDir, pluginName);
 
-    Try<Nothing> write = os::write(mockPlugin, pluginScript);
+    Try<Nothing> write = os::write(pluginPath, pluginScript);
     if (write.isError()) {
-      return Error("Failed to write '" + mockPlugin + "': " + write.error());
+      return Error("Failed to write '" + pluginPath + "': " + write.error());
     }
 
     // Make sure the plugin has execution permission.
     Try<Nothing> chmod = os::chmod(
-        mockPlugin,
+        pluginPath,
         S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
 
     if (chmod.isError()) {
-      return Error("Failed to chmod '" + mockPlugin + "': " + chmod.error());
+      return Error("Failed to chmod '" + pluginPath + "': " + chmod.error());
     }
 
     return Nothing();
@@ -2346,6 +2425,117 @@ TEST_P(DefaultContainerDNSCniTest, ROOT_VerifyDefaultDNS)
   driver.join();
 }
 
+
+// This test verifies the ResourceStatistics.
+TEST_F(CniIsolatorTest, VETH_VerifyResourceStatistics)
+{
+  Try<Owned<cluster::Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  slave::Flags flags = CreateSlaveFlags();
+  flags.isolation = "network/cni";
+
+  flags.network_cni_plugins_dir = cniPluginDir;
+  flags.network_cni_config_dir = cniConfigDir;
+
+  Fetcher fetcher(flags);
+
+  Try<MesosContainerizer*> _containerizer =
+    MesosContainerizer::create(flags, true, &fetcher);
+
+  ASSERT_SOME(_containerizer);
+  Owned<MesosContainerizer> containerizer(_containerizer.get());
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+
+  Try<Owned<cluster::Slave>> slave =
+    StartSlave(detector.get(), containerizer.get(), flags);
+  ASSERT_SOME(slave);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(&driver, _, _));
+
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  driver.start();
+
+  AWAIT_READY(offers);
+  ASSERT_EQ(1u, offers->size());
+
+  const Offer& offer = offers.get()[0];
+
+  CommandInfo command;
+  command.set_value("sleep 1000");
+
+  TaskInfo task = createTask(
+      offer.slave_id(),
+      Resources::parse("cpus:1;mem:128").get(),
+      command);
+
+  ContainerInfo* container = task.mutable_container();
+  container->set_type(ContainerInfo::MESOS);
+
+  // Make sure the container joins the mock CNI network.
+  container->add_network_infos()->set_name("veth");
+
+  Future<TaskStatus> statusStarting;
+  Future<TaskStatus> statusRunning;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&statusStarting))
+    .WillOnce(FutureArg<1>(&statusRunning));
+
+  driver.launchTasks(offer.id(), {task});
+
+  AWAIT_READY(statusStarting);
+  EXPECT_EQ(task.task_id(), statusStarting->task_id());
+  EXPECT_EQ(TASK_STARTING, statusStarting->state());
+
+  AWAIT_READY(statusRunning);
+  EXPECT_EQ(task.task_id(), statusRunning->task_id());
+  EXPECT_EQ(TASK_RUNNING, statusRunning->state());
+
+  Future<hashset<ContainerID>> containers = containerizer->containers();
+  AWAIT_READY(containers);
+  ASSERT_EQ(1u, containers->size());
+
+  ContainerID containerId = *(containers->begin());
+
+  // Verify networking metrics.
+  Future<ResourceStatistics> usage = containerizer.get()->usage(containerId);
+  AWAIT_READY(usage);
+
+  // RX: Receive statistics.
+  ASSERT_TRUE(usage->has_net_rx_packets());
+  ASSERT_TRUE(usage->has_net_rx_bytes());
+  ASSERT_TRUE(usage->has_net_rx_errors());
+  ASSERT_TRUE(usage->has_net_rx_dropped());
+
+  // TX: Transmit statistics.
+  ASSERT_TRUE(usage->has_net_tx_packets());
+  ASSERT_TRUE(usage->has_net_tx_bytes());
+  ASSERT_TRUE(usage->has_net_tx_errors());
+  ASSERT_TRUE(usage->has_net_tx_dropped());
+
+  // Kill the task.
+  Future<TaskStatus> statusKilled;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&statusKilled));
+
+  driver.killTask(task.task_id());
+
+  AWAIT_READY(statusKilled);
+  EXPECT_EQ(TASK_KILLED, statusKilled->state());
+
+  driver.stop();
+  driver.join();
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {
diff --git a/src/tests/environment.cpp b/src/tests/environment.cpp
index 8c6ec58..bc05e4b 100644
--- a/src/tests/environment.cpp
+++ b/src/tests/environment.cpp
@@ -973,6 +973,79 @@ private:
 };
 
 
+// This is a test filter for the veth CNI plugin.
+class VEthFilter : public TestFilter
+{
+public:
+  VEthFilter()
+  {
+#ifdef __linux__
+    vector<string> messages;
+
+    // Checking if it runs as root.
+    Result<string> user = os::user();
+    CHECK_SOME(user);
+
+    if (user.get() != "root") {
+      messages.emplace_back("non-root user");
+    }
+
+    // This command returns `ip utility, iproute2-YYMMDD` where
+    // `YYMMDD` is a release (snapshot) date of iproute2.
+    Try<string> ipVersion = os::shell("ip -Version");
+
+    // Checking if iproute2 exists.
+    if (ipVersion.isError()) {
+      messages.emplace_back("iproute2 not found");
+    } else {
+      // Checking if it supports `ip link set ... netns ...`.
+      const string version = strings::trim(ipVersion.get());
+      if (version.size() < 6) {
+        messages.emplace_back("unexpected version");
+      } else {
+        Try<int> snapshot = numify<int>(version.substr(version.size() - 6));
+        if (snapshot.isError()) {
+          messages.emplace_back("iproute2 version is not an integer");
+        } else if (snapshot.get() < 100224) {
+          // Support for `netns` was added to iproute2 in v2.6.33.
+          messages.emplace_back("iproute2 doesn't support network namespaces");
+        }
+      }
+    }
+
+    // Checking if libprocess is bound on loopback address, in that
+    // case network namespace with veth network won't be able to
+    // connect to parent process on host network namespace.
+    // TODO(urbanserj): Improve the network connectivity check.
+    process::network::inet::Address address = process::address();
+    if (address.ip.isLoopback()) {
+      messages.emplace_back("libprocess is bound on loopback address");
+    }
+
+    disabled = !messages.empty();
+    if (disabled) {
+      std::cerr
+        << "-------------------------------------------------------------\n"
+        << "We can't run any VETH tests:\n"
+        << strings::join("\n", messages) << "\n"
+        << "-------------------------------------------------------------"
+        << std::endl;
+    }
+#else
+    disabled = true;
+#endif // __linux__
+  }
+
+  bool disable(const ::testing::TestInfo* test) const override
+  {
+    return matches(test, "VETH_") && disabled;
+  }
+
+private:
+  bool disabled;
+};
+
+
 Environment::Environment(const Flags& _flags)
   : stout::internal::tests::Environment(
         std::vector<std::shared_ptr<TestFilter>>{
@@ -996,6 +1069,7 @@ Environment::Environment(const Flags& _flags)
             std::make_shared<RootFilter>(),
             std::make_shared<UnprivilegedUserFilter>(),
             std::make_shared<UnzipFilter>(),
+            std::make_shared<VEthFilter>(),
             std::make_shared<XfsFilter>()}),
     flags(_flags)
 {


Mime
View raw message