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 {