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 2016/12/22 01:51:05 UTC

[1/4] mesos git commit: Modified isolator for dynamic addition/deletion of CNI networks.

Repository: mesos
Updated Branches:
  refs/heads/master 2cb0b9257 -> e6005743e


Modified isolator for dynamic addition/deletion of CNI networks.

If the `network/cni` isolator sees a cache-miss during the `prepare`
phase, it will try to look for the CNI network on disk before giving
up. This allows for the dynamic addition of CNI networks without the
need for agent restart.

During `isolate` or `prepare` if for a given network, the isolator is
unable to read the corresponding configuration file, or finds and
error in the existing configuration file, it will remove the network
from the in-memory cache. This allows dynamic deletion of CNI
configurations from the `network/cni` isolator.

Review: https://reviews.apache.org/r/54717/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/89547353
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/89547353
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/89547353

Branch: refs/heads/master
Commit: 89547353d6da02de5ae2ca3edd4a8dc85fa54234
Parents: 2cb0b92
Author: Avinash sridharan <av...@mesosphere.io>
Authored: Wed Dec 21 14:10:51 2016 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Dec 21 14:35:04 2016 -0800

----------------------------------------------------------------------
 .../mesos/isolators/network/cni/cni.cpp         | 158 ++++++++++++++-----
 .../mesos/isolators/network/cni/cni.hpp         |  15 ++
 2 files changed, 132 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/89547353/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
index abc0033..7853809 100644
--- a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
+++ b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
@@ -243,6 +243,93 @@ Try<Isolator*> NetworkCniIsolatorProcess::create(const Flags& flags)
 }
 
 
+Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
+    const string& network,
+    const string& path)
+{
+  Try<string> read = os::read(path);
+  if (read.isError()) {
+    return Error(
+        "Failed to read CNI network configuration file: '" +
+        path + "': " + read.error());
+  }
+
+  Try<JSON::Object> parse = JSON::parse<JSON::Object>(read.get());
+  if (parse.isError()) {
+    return Error(
+        "Failed to parse CNI network configuration file: '" +
+        path + "': " + parse.error());
+  }
+
+  Result<JSON::String> name = parse->at<JSON::String>("name");
+  if (!name.isSome()) {
+    return Error(
+        "Cannot determine the 'name' of the CNI network for this "
+        "configuration " +
+        (name.isNone() ? "'" : ("': " + name.error())));
+  }
+
+  // Verify the configuration is for this network
+  if (network != name->value) {
+    return Error(
+        "The current CNI configuration network('" + name->value +
+        "') does not match the network name: '" + network + "'");
+  }
+
+  return parse;
+}
+
+
+Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
+    const string& network)
+{
+  if (networkConfigs.contains(network)) {
+    // Make sure the JSON is valid.
+    Try<JSON::Object> config = getNetworkConfigJSON(
+        network,
+        networkConfigs[network]);
+
+    if (config.isError()) {
+      LOG(WARNING) << "Removing the network '" << network
+                   << "' from cache due to failure to validate "
+                   << "the configuration: " << config.error();
+
+      networkConfigs.erase(network);
+
+      // Fall-through and do a reload.
+    } else {
+      return config;
+    }
+  }
+
+  // Cache-miss.
+  Try<hashmap<string, string>> _networkConfigs = loadNetworkConfigs(
+      flags.network_cni_config_dir.get(),
+      flags.network_cni_plugins_dir.get());
+
+  if (_networkConfigs.isError()) {
+      return Error(
+          "Encountered error while loading CNI config during "
+          "a cache-miss for CNI network '" + network +"': " +
+          _networkConfigs.error());
+  }
+
+  networkConfigs = _networkConfigs.get();
+
+  // Do another search.
+  if (networkConfigs.contains(network)) {
+    // This is a best-effort retrieval of the CNI network config. So
+    // if it fails in this attempt just return the `Error` instead of
+    // trying to erase the network from cache. Deletion of the
+    // network, in case of an error, will happen on its own in the
+    // next attempt.
+    return getNetworkConfigJSON(network, networkConfigs[network]);
+  }
+
+  return Error("Unknown CNI network '" + network + "'");
+}
+
+
 Try<hashmap<string, string>> NetworkCniIsolatorProcess::loadNetworkConfigs(
     const string& configDir,
     const string& pluginDir)
@@ -282,6 +369,7 @@ Try<hashmap<string, string>> NetworkCniIsolatorProcess::loadNetworkConfigs(
 
     const spec::NetworkConfig& networkConfig = parse.get();
     const string& name = networkConfig.name();
+
     if (networkConfigs.contains(name)) {
       LOG(ERROR) << "Multiple network configuration for a CNI network is not "
                  << "allowed. Skipping configuration file '"
@@ -561,8 +649,10 @@ Future<Option<ContainerLaunchInfo>> NetworkCniIsolatorProcess::prepare(
       }
 
       const string& name = networkInfo.name();
-      if (!networkConfigs.contains(name)) {
-        return Failure("Unknown CNI network '" + name + "'");
+
+      Try<JSON::Object> networkConfigJSON = getNetworkConfigJSON(name);
+      if (networkConfigJSON.isError()) {
+        return Failure(networkConfigJSON.error());
       }
 
       if (containerNetworks.contains(name)) {
@@ -1077,6 +1167,13 @@ Future<Nothing> NetworkCniIsolatorProcess::attach(
   CHECK(infos.contains(containerId));
   CHECK(infos[containerId]->containerNetworks.contains(networkName));
 
+  Try<JSON::Object> networkConfigJSON = getNetworkConfigJSON(networkName);
+  if (networkConfigJSON.isError()) {
+    return Failure(
+        "Could not get valid CNI configuration for network '" + networkName +
+        "': " + networkConfigJSON.error());
+  }
+
   const ContainerNetwork& containerNetwork =
     infos[containerId]->containerNetworks[networkName];
 
@@ -1091,7 +1188,7 @@ Future<Nothing> NetworkCniIsolatorProcess::attach(
     return Failure(
         "Failed to create interface directory for the interface '" +
         containerNetwork.ifName + "' of the network '" +
-        containerNetwork.networkName + "': "+ mkdir.error());
+        networkName + "': "+ mkdir.error());
   }
 
   // Prepare environment variables for CNI plugin.
@@ -1116,32 +1213,13 @@ Future<Nothing> NetworkCniIsolatorProcess::attach(
   // Inject Mesos metadata to the network configuration JSON that will
   // be passed to the plugin. Currently, we only pass in NetworkInfo
   // for the given network.
-  const string& cniConfigPath =
-    networkConfigs[containerNetwork.networkName];
-
-  Try<string> read = os::read(cniConfigPath);
-  if (read.isError()) {
-    return Failure(
-        "Failed to read CNI network configuration file: '" +
-        cniConfigPath + "': " + read.error());
-  }
-
-  Try<JSON::Object> parse = JSON::parse<JSON::Object>(read.get());
-  if (parse.isError()) {
-    return Failure(
-        "Failed to parse CNI network configuration file: '" +
-        cniConfigPath  + "': " + parse.error());
-  }
-
-  JSON::Object networkConfigJson = parse.get();
-
   // Note that 'args' might or might not be specified in the network
   // configuration file. We need to deal with both cases.
-  Result<JSON::Object> _args = networkConfigJson.at<JSON::Object>("args");
+  Result<JSON::Object> _args = networkConfigJSON->at<JSON::Object>("args");
   if (_args.isError()) {
     return Failure(
         "Invalid 'args' found in CNI network configuration file '" +
-        cniConfigPath  + "': " + _args.error());
+        networkConfigs[networkName] + "': " + _args.error());
   }
 
   JSON::Object args = _args.isSome() ? _args.get() : JSON::Object();
@@ -1159,17 +1237,18 @@ Future<Nothing> NetworkCniIsolatorProcess::attach(
   JSON::Object mesos;
   mesos.values["network_info"] = JSON::protobuf(networkInfo);
   args.values["org.apache.mesos"] = mesos;
-  networkConfigJson.values["args"] = args;
+  networkConfigJSON->values["args"] = args;
 
   // Invoke the CNI plugin.
   //
   // NOTE: We want to execute only the plugin found in the `pluginDir`
   // path specified by the operator.
-  Result<JSON::String> _plugin = networkConfigJson.at<JSON::String>("type");
+  Result<JSON::String> _plugin = networkConfigJSON->at<JSON::String>("type");
   if (!_plugin.isSome()) {
     return Failure(
-        "Could not find the CNI plugin to use for network " +
-        networkName + " with CNI configuration '" + cniConfigPath  +
+        "Could not find the CNI plugin to use for network '" +
+        networkName + "' with CNI configuration '" +
+        networkConfigs[networkName] +
         (_plugin.isNone() ? "'" : ("': " + _plugin.error())));
   }
 
@@ -1192,16 +1271,17 @@ Future<Nothing> NetworkCniIsolatorProcess::attach(
       networkName);
 
   Try<Nothing> write =
-    os::write(networkConfigPath, stringify(networkConfigJson));
+    os::write(networkConfigPath, stringify(networkConfigJSON.get()));
 
   if (write.isError()) {
     return Failure(
         "Failed to checkpoint the CNI network configuration '" +
-        stringify(networkConfigJson) + "': " + write.error());
+        stringify(networkConfigJSON.get()) + "': " + write.error());
   }
 
   VLOG(1) << "Invoking CNI plugin '" << plugin.get()
-          << "' with network configuration '" << stringify(networkConfigJson)
+          << "' with network configuration '"
+          << stringify(networkConfigJSON.get())
           << "' to attach container " << containerId << " to network '"
           << networkName << "'";
 
@@ -1521,21 +1601,17 @@ Future<Nothing> NetworkCniIsolatorProcess::detach(
       containerId.value(),
       networkName);
 
-  Try<string> read = os::read(networkConfigPath);
-  if (read.isError()) {
-    return Failure(
-        "Could not detach the container " + stringify(containerId) +
-        " since we cannot read its checkpoitned CNI config: " + read.error());
-  }
+  Try<JSON::Object> networkConfigJSON = getNetworkConfigJSON(
+      networkName,
+      networkConfigPath);
 
-  Try<JSON::Object> networkConfigJson = JSON::parse<JSON::Object>(read.get());
-  if (networkConfigJson.isError()) {
+  if (networkConfigJSON.isError()) {
     return Failure(
         "Failed to parse CNI network configuration file: '" +
-        networkConfigPath + "': " + networkConfigJson.error());
+        networkConfigPath + "': " + networkConfigJSON.error());
   }
 
-  Result<JSON::String> _plugin = networkConfigJson->at<JSON::String>("type");
+  Result<JSON::String> _plugin = networkConfigJSON->at<JSON::String>("type");
   if (!_plugin.isSome()) {
     return Failure(
         "Could not find the CNI plugin to use for network " +

http://git-wip-us.apache.org/repos/asf/mesos/blob/89547353/src/slave/containerizer/mesos/isolators/network/cni/cni.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/network/cni/cni.hpp b/src/slave/containerizer/mesos/isolators/network/cni/cni.hpp
index 649929a..276b900 100644
--- a/src/slave/containerizer/mesos/isolators/network/cni/cni.hpp
+++ b/src/slave/containerizer/mesos/isolators/network/cni/cni.hpp
@@ -172,6 +172,21 @@ private:
       const ContainerID& containerId,
       const std::list<process::Future<Nothing>>& detaches);
 
+  // Searches the `networkConfigs` hashmap for a CNI network. If the
+  // hashmap doesn't contain the network, will try to load all the CNI
+  // configs from `flags.network_cni_config_dir`, and will then
+  // perform another search of the `networkConfigs` hashmap to see if
+  // the missing network was present on disk.
+  Try<JSON::Object> getNetworkConfigJSON(const std::string& network);
+
+  // Given a network name and the path for the CNI network
+  // configuration file, reads the file, parses the JSON and
+  // validates the name of the network to which this configuration
+  // file belongs.
+  Try<JSON::Object> getNetworkConfigJSON(
+      const std::string& network,
+      const std::string& path);
+
   const Flags flags;
 
   // A map storing the path to CNI network configuration files keyed


[2/4] mesos git commit: Reordered the functions in cni.cpp to be consistent with the header.

Posted by ji...@apache.org.
Reordered the functions in cni.cpp to be consistent with the header.


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/73a860a9
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/73a860a9
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/73a860a9

Branch: refs/heads/master
Commit: 73a860a9b6d426f85704b2fff8381438e98fde8f
Parents: 8954735
Author: Jie Yu <yu...@gmail.com>
Authored: Wed Dec 21 15:02:30 2016 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Dec 21 15:02:30 2016 -0800

----------------------------------------------------------------------
 .../mesos/isolators/network/cni/cni.cpp         | 174 +++++++++----------
 1 file changed, 87 insertions(+), 87 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/73a860a9/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
index 7853809..f9cea8b 100644
--- a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
+++ b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
@@ -243,93 +243,6 @@ Try<Isolator*> NetworkCniIsolatorProcess::create(const Flags& flags)
 }
 
 
-Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
-    const string& network,
-    const string& path)
-{
-  Try<string> read = os::read(path);
-  if (read.isError()) {
-    return Error(
-        "Failed to read CNI network configuration file: '" +
-        path + "': " + read.error());
-  }
-
-  Try<JSON::Object> parse = JSON::parse<JSON::Object>(read.get());
-  if (parse.isError()) {
-    return Error(
-        "Failed to parse CNI network configuration file: '" +
-        path + "': " + parse.error());
-  }
-
-  Result<JSON::String> name = parse->at<JSON::String>("name");
-  if (!name.isSome()) {
-    return Error(
-        "Cannot determine the 'name' of the CNI network for this "
-        "configuration " +
-        (name.isNone() ? "'" : ("': " + name.error())));
-  }
-
-  // Verify the configuration is for this network
-  if (network != name->value) {
-    return Error(
-        "The current CNI configuration network('" + name->value +
-        "') does not match the network name: '" + network + "'");
-  }
-
-  return parse;
-}
-
-
-Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
-    const string& network)
-{
-  if (networkConfigs.contains(network)) {
-    // Make sure the JSON is valid.
-    Try<JSON::Object> config = getNetworkConfigJSON(
-        network,
-        networkConfigs[network]);
-
-    if (config.isError()) {
-      LOG(WARNING) << "Removing the network '" << network
-                   << "' from cache due to failure to validate "
-                   << "the configuration: " << config.error();
-
-      networkConfigs.erase(network);
-
-      // Fall-through and do a reload.
-    } else {
-      return config;
-    }
-  }
-
-  // Cache-miss.
-  Try<hashmap<string, string>> _networkConfigs = loadNetworkConfigs(
-      flags.network_cni_config_dir.get(),
-      flags.network_cni_plugins_dir.get());
-
-  if (_networkConfigs.isError()) {
-      return Error(
-          "Encountered error while loading CNI config during "
-          "a cache-miss for CNI network '" + network +"': " +
-          _networkConfigs.error());
-  }
-
-  networkConfigs = _networkConfigs.get();
-
-  // Do another search.
-  if (networkConfigs.contains(network)) {
-    // This is a best-effort retrieval of the CNI network config. So
-    // if it fails in this attempt just return the `Error` instead of
-    // trying to erase the network from cache. Deletion of the
-    // network, in case of an error, will happen on its own in the
-    // next attempt.
-    return getNetworkConfigJSON(network, networkConfigs[network]);
-  }
-
-  return Error("Unknown CNI network '" + network + "'");
-}
-
-
 Try<hashmap<string, string>> NetworkCniIsolatorProcess::loadNetworkConfigs(
     const string& configDir,
     const string& pluginDir)
@@ -1730,6 +1643,93 @@ Future<Nothing> NetworkCniIsolatorProcess::_detach(
 }
 
 
+Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
+    const string& network,
+    const string& path)
+{
+  Try<string> read = os::read(path);
+  if (read.isError()) {
+    return Error(
+        "Failed to read CNI network configuration file: '" +
+        path + "': " + read.error());
+  }
+
+  Try<JSON::Object> parse = JSON::parse<JSON::Object>(read.get());
+  if (parse.isError()) {
+    return Error(
+        "Failed to parse CNI network configuration file: '" +
+        path + "': " + parse.error());
+  }
+
+  Result<JSON::String> name = parse->at<JSON::String>("name");
+  if (!name.isSome()) {
+    return Error(
+        "Cannot determine the 'name' of the CNI network for this "
+        "configuration " +
+        (name.isNone() ? "'" : ("': " + name.error())));
+  }
+
+  // Verify the configuration is for this network
+  if (network != name->value) {
+    return Error(
+        "The current CNI configuration network('" + name->value +
+        "') does not match the network name: '" + network + "'");
+  }
+
+  return parse;
+}
+
+
+Try<JSON::Object> NetworkCniIsolatorProcess::getNetworkConfigJSON(
+    const string& network)
+{
+  if (networkConfigs.contains(network)) {
+    // Make sure the JSON is valid.
+    Try<JSON::Object> config = getNetworkConfigJSON(
+        network,
+        networkConfigs[network]);
+
+    if (config.isError()) {
+      LOG(WARNING) << "Removing the network '" << network
+                   << "' from cache due to failure to validate "
+                   << "the configuration: " << config.error();
+
+      networkConfigs.erase(network);
+
+      // Fall-through and do a reload.
+    } else {
+      return config;
+    }
+  }
+
+  // Cache-miss.
+  Try<hashmap<string, string>> _networkConfigs = loadNetworkConfigs(
+      flags.network_cni_config_dir.get(),
+      flags.network_cni_plugins_dir.get());
+
+  if (_networkConfigs.isError()) {
+      return Error(
+          "Encountered error while loading CNI config during "
+          "a cache-miss for CNI network '" + network +"': " +
+          _networkConfigs.error());
+  }
+
+  networkConfigs = _networkConfigs.get();
+
+  // Do another search.
+  if (networkConfigs.contains(network)) {
+    // This is a best-effort retrieval of the CNI network config. So
+    // if it fails in this attempt just return the `Error` instead of
+    // trying to erase the network from cache. Deletion of the
+    // network, in case of an error, will happen on its own in the
+    // next attempt.
+    return getNetworkConfigJSON(network, networkConfigs[network]);
+  }
+
+  return Error("Unknown CNI network '" + network + "'");
+}
+
+
 // Implementation of subcommand to setup relevant network files and
 // hostname in the container UTS and mount namespace.
 const char* NetworkCniIsolatorSetup::NAME = "network-cni-setup";


[3/4] mesos git commit: Removed dependency of `recover` on cached networks.

Posted by ji...@apache.org.
Removed dependency of `recover` on cached networks.

Since CNI networks can be added and removed on the fly, and for a
given container the CNI configuration with which the container was
launched is check-pointed in the containers \`network/cni\` work
directory, it doesn't make sense to require the network name that the
container is associated with to exist in the in-memory cache. Hence,
removing these dependencies.

Review: https://reviews.apache.org/r/54718/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/f74d9a1d
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/f74d9a1d
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/f74d9a1d

Branch: refs/heads/master
Commit: f74d9a1dfb03319a50811bddde5c9a635529dbae
Parents: 73a860a
Author: Avinash sridharan <av...@mesosphere.io>
Authored: Wed Dec 21 15:02:45 2016 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Dec 21 15:02:45 2016 -0800

----------------------------------------------------------------------
 src/slave/containerizer/mesos/isolators/network/cni/cni.cpp | 4 ----
 1 file changed, 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/f74d9a1d/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
index f9cea8b..ea91c71 100644
--- a/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
+++ b/src/slave/containerizer/mesos/isolators/network/cni/cni.cpp
@@ -439,10 +439,6 @@ Try<Nothing> NetworkCniIsolatorProcess::_recover(
 
   hashmap<string, ContainerNetwork> containerNetworks;
   foreach (const string& networkName, networkNames.get()) {
-    if (!networkConfigs.contains(networkName)) {
-      return Error("Unknown CNI network name '" + networkName + "'");
-    }
-
     Try<list<string>> interfaces = paths::getInterfaces(
         rootDir.get(),
         containerId.value(),


[4/4] mesos git commit: Added unit-test for dynamic addition/deletion of CNI config.

Posted by ji...@apache.org.
Added unit-test for dynamic addition/deletion of CNI config.

Review: https://reviews.apache.org/r/54880/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/e6005743
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/e6005743
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/e6005743

Branch: refs/heads/master
Commit: e6005743ea247f381c055dce2e875730b07a3864
Parents: f74d9a1
Author: Avinash sridharan <av...@mesosphere.io>
Authored: Wed Dec 21 15:02:55 2016 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Wed Dec 21 17:50:57 2016 -0800

----------------------------------------------------------------------
 src/tests/containerizer/cni_isolator_tests.cpp | 168 ++++++++++++++++++++
 1 file changed, 168 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/e6005743/src/tests/containerizer/cni_isolator_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/containerizer/cni_isolator_tests.cpp b/src/tests/containerizer/cni_isolator_tests.cpp
index eb19407..82697ae 100644
--- a/src/tests/containerizer/cni_isolator_tests.cpp
+++ b/src/tests/containerizer/cni_isolator_tests.cpp
@@ -16,6 +16,8 @@
 
 #include <gmock/gmock.h>
 
+#include <process/clock.hpp>
+
 #include <stout/ip.hpp>
 
 #include "slave/containerizer/fetcher.hpp"
@@ -35,6 +37,7 @@ using mesos::internal::slave::MesosContainerizer;
 
 using mesos::master::detector::MasterDetector;
 
+using process::Clock;
 using process::Future;
 using process::Owned;
 
@@ -682,6 +685,171 @@ TEST_F(CniIsolatorTest, ROOT_INTERNET_CURL_LaunchContainerInHostNetwork)
   driver.join();
 }
 
+
+// This tests the dynamic addition and deletion of CNI configuration
+// without the need to restart the agent.
+TEST_F(CniIsolatorTest, ROOT_DynamicAddDelofCniConfig)
+{
+  master::Flags masterFlags = CreateMasterFlags();
+
+  Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  slave::Flags slaveFlags = CreateSlaveFlags();
+
+  slaveFlags.network_cni_plugins_dir = cniPluginDir;
+  slaveFlags.network_cni_config_dir = cniConfigDir;
+
+  Try<string> mockCniConfig = os::read(path::join(cniConfigDir, "mockConfig"));
+  ASSERT_SOME(mockCniConfig);
+
+  // Remove the CNI config.
+  Try<Nothing> rm = os::rm(path::join(cniConfigDir, "mockConfig"));
+  ASSERT_SOME(rm);
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+
+  Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
+  ASSERT_SOME(slave);
+
+  MockScheduler sched;
+
+  // We use the filter explicitly here so that the resources will not
+  // be filtered for 5 seconds (the default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+
+  MesosSchedulerDriver driver(
+      &sched,
+      frameworkInfo,
+      master.get()->pid,
+      DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(_, _, _));
+
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
+
+  driver.start();
+
+  AWAIT_READY(offers);
+  ASSERT_EQ(1u, offers->size());
+
+  const Offer& offer1 = offers.get()[0];
+
+  CommandInfo command = createCommandInfo("sleep 1000");
+
+  TaskInfo task = createTask(
+      offer1.slave_id(),
+      Resources::parse("cpus:0.1;mem:128").get(),
+      command);
+
+  ContainerInfo* container = task.mutable_container();
+  container->set_type(ContainerInfo::MESOS);
+
+  // Make sure the container is not able to join mock CNI network.
+  container->add_network_infos()->set_name("__MESOS_TEST__");
+
+  Future<TaskStatus> statusFailed;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&statusFailed));
+
+  driver.launchTasks(offer1.id(), {task}, filters);
+
+  AWAIT_READY_FOR(statusFailed, Seconds(60));
+  EXPECT_EQ(task.task_id(), statusFailed->task_id());
+  EXPECT_EQ(TASK_FAILED, statusFailed->state());
+
+  // Now add the CNI config back. This would dynamically add the CNI
+  // network to the `network/cni` isolator, and try launching a task
+  // on this CNI network.
+  Try<Nothing> write = os::write(
+      path::join(cniConfigDir, "mockConfig"),
+      mockCniConfig.get());
+
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
+
+  Clock::pause();
+  Clock::advance(masterFlags.allocation_interval);
+  Clock::settle();
+  Clock::resume();
+
+  AWAIT_READY(offers);
+  ASSERT_EQ(1u, offers->size());
+
+  const Offer& offer2 = offers.get()[0];
+
+  task = createTask(
+      offer2.slave_id(),
+      Resources::parse("cpus:0.1;mem:128").get(),
+      command);
+
+  container = task.mutable_container();
+  container->set_type(ContainerInfo::MESOS);
+
+  // Make sure the container is able to join mock CNI network.
+  container->add_network_infos()->set_name("__MESOS_TEST__");
+
+  Future<TaskStatus> statusRunning;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&statusRunning));
+
+  driver.launchTasks(offer2.id(), {task}, filters);
+
+  AWAIT_READY_FOR(statusRunning, Seconds(60));
+  EXPECT_EQ(task.task_id(), statusRunning->task_id());
+  EXPECT_EQ(TASK_RUNNING, statusRunning->state());
+
+  // Testing dynamic deletion of CNI networks.
+  rm = os::rm(path::join(cniConfigDir, "mockConfig"));
+  ASSERT_SOME(rm);
+
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(DeclineOffers()); // Ignore subsequent offers.
+
+  Clock::pause();
+  Clock::advance(Seconds(10));
+  Clock::settle();
+  Clock::resume();
+
+  // Try launching the task on the `__MESOS_TEST__` network, it should
+  // fail because the network config has been deleted.
+  AWAIT_READY(offers);
+  ASSERT_EQ(1u, offers->size());
+
+  const Offer& offer3 = offers.get()[0];
+
+  task = createTask(
+      offer3.slave_id(),
+      Resources::parse("cpus:0.1;mem:128").get(),
+      command);
+
+  container = task.mutable_container();
+  container->set_type(ContainerInfo::MESOS);
+
+  // Make sure the container is not able to join mock CNI network.
+  container->add_network_infos()->set_name("__MESOS_TEST__");
+
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&statusFailed));
+
+  driver.launchTasks(offer3.id(), {task}, filters);
+
+  AWAIT_READY_FOR(statusFailed, Seconds(60));
+  EXPECT_EQ(task.task_id(), statusFailed->task_id());
+  EXPECT_EQ(TASK_FAILED, statusFailed->state());
+
+  driver.stop();
+  driver.join();
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {