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 2017/12/11 23:31:54 UTC
[2/4] mesos git commit: Added unit tests for resource provider config
modification API.
Added unit tests for resource provider config modification API.
Review: https://reviews.apache.org/r/64469/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/2c606ae6
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/2c606ae6
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/2c606ae6
Branch: refs/heads/master
Commit: 2c606ae6dde368089adb755eb9413af3967fffec
Parents: b9073b8
Author: Chun-Hung Hsiao <ch...@mesosphere.io>
Authored: Mon Dec 11 15:09:38 2017 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Mon Dec 11 15:09:38 2017 -0800
----------------------------------------------------------------------
include/mesos/type_utils.hpp | 8 +
include/mesos/v1/mesos.hpp | 8 +
src/Makefile.am | 1 +
src/resource_provider/manager.cpp | 6 +
...agent_resource_provider_config_api_tests.cpp | 670 +++++++++++++++++++
5 files changed, 693 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/2c606ae6/include/mesos/type_utils.hpp
----------------------------------------------------------------------
diff --git a/include/mesos/type_utils.hpp b/include/mesos/type_utils.hpp
index 506f667..1bcc521 100644
--- a/include/mesos/type_utils.hpp
+++ b/include/mesos/type_utils.hpp
@@ -286,6 +286,14 @@ inline bool operator!=(const SlaveID& left, const SlaveID& right)
}
+inline bool operator!=(
+ const ResourceProviderInfo& left,
+ const ResourceProviderInfo& right)
+{
+ return !(left == right);
+}
+
+
inline bool operator!=(const TimeInfo& left, const TimeInfo& right)
{
return !(left == right);
http://git-wip-us.apache.org/repos/asf/mesos/blob/2c606ae6/include/mesos/v1/mesos.hpp
----------------------------------------------------------------------
diff --git a/include/mesos/v1/mesos.hpp b/include/mesos/v1/mesos.hpp
index f393ed5..d163f0b 100644
--- a/include/mesos/v1/mesos.hpp
+++ b/include/mesos/v1/mesos.hpp
@@ -271,6 +271,14 @@ inline bool operator!=(
}
+inline bool operator!=(
+ const ResourceProviderInfo& left,
+ const ResourceProviderInfo& right)
+{
+ return !(left == right);
+}
+
+
inline bool operator!=(const AgentID& left, const AgentID& right)
{
return left.value() != right.value();
http://git-wip-us.apache.org/repos/asf/mesos/blob/2c606ae6/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 47f4528..f5a4edd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2621,6 +2621,7 @@ mesos_tests_SOURCES += \
if OS_LINUX
mesos_tests_SOURCES += \
+ tests/agent_resource_provider_config_api_tests.cpp \
tests/storage_local_resource_provider_tests.cpp
endif
endif
http://git-wip-us.apache.org/repos/asf/mesos/blob/2c606ae6/src/resource_provider/manager.cpp
----------------------------------------------------------------------
diff --git a/src/resource_provider/manager.cpp b/src/resource_provider/manager.cpp
index bfc917f..fd138b9 100644
--- a/src/resource_provider/manager.cpp
+++ b/src/resource_provider/manager.cpp
@@ -131,6 +131,8 @@ struct ResourceProvider
~ResourceProvider()
{
+ LOG(INFO) << "Terminating resource provider " << info.id();
+
http.close();
foreachvalue (const Owned<Promise<Nothing>>& publish, publishes) {
@@ -661,6 +663,10 @@ void ResourceProviderManagerProcess::updateState(
offerOperations.put(uuid.get(), operation);
}
+ LOG(INFO)
+ << "Received UPDATE_STATE call with resources '" << update.resources()
+ << "' from resource provider " << resourceProvider->info.id();
+
ResourceProviderMessage::UpdateState updateState{
resourceProvider->info,
resourceVersion.get(),
http://git-wip-us.apache.org/repos/asf/mesos/blob/2c606ae6/src/tests/agent_resource_provider_config_api_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/agent_resource_provider_config_api_tests.cpp b/src/tests/agent_resource_provider_config_api_tests.cpp
new file mode 100644
index 0000000..5a50e82
--- /dev/null
+++ b/src/tests/agent_resource_provider_config_api_tests.cpp
@@ -0,0 +1,670 @@
+// 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 <mesos/type_utils.hpp>
+
+#include <process/gtest.hpp>
+#include <process/gmock.hpp>
+
+#include <stout/fs.hpp>
+#include <stout/json.hpp>
+#include <stout/protobuf.hpp>
+#include <stout/stringify.hpp>
+
+#include "common/http.hpp"
+
+#include "internal/evolve.hpp"
+
+#include "slave/slave.hpp"
+
+#include "tests/flags.hpp"
+#include "tests/mesos.hpp"
+
+namespace http = process::http;
+
+using std::list;
+using std::string;
+using std::vector;
+
+using mesos::internal::slave::Slave;
+
+using mesos::master::detector::MasterDetector;
+
+using process::Future;
+using process::Owned;
+using process::PID;
+
+using testing::Values;
+using testing::WithParamInterface;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class AgentResourceProviderConfigApiTest
+ : public MesosTest,
+ public WithParamInterface<ContentType>
+{
+public:
+ virtual void SetUp()
+ {
+ MesosTest::SetUp();
+
+ resourceProviderConfigDir =
+ path::join(sandbox.get(), "resource_provider_configs");
+
+ ASSERT_SOME(os::mkdir(resourceProviderConfigDir));
+ }
+
+ ResourceProviderInfo createResourceProviderInfo(const Bytes& capacity)
+ {
+ const string testCsiPluginWorkDir = path::join(sandbox.get(), "test");
+ CHECK_SOME(os::mkdir(testCsiPluginWorkDir));
+
+ string testCsiPluginPath =
+ path::join(tests::flags.build_dir, "src", "test-csi-plugin");
+
+ Try<string> resourceProviderConfig = strings::format(
+ R"~(
+ {
+ "type": "org.apache.mesos.rp.local.storage",
+ "name": "test",
+ "default_reservations": [
+ {
+ "type": "DYNAMIC",
+ "role": "storage"
+ }
+ ],
+ "storage": {
+ "plugin": {
+ "type": "org.apache.mesos.csi.test",
+ "name": "slrp_test",
+ "containers": [
+ {
+ "services": [
+ "CONTROLLER_SERVICE",
+ "NODE_SERVICE"
+ ],
+ "command": {
+ "shell": false,
+ "value": "%s",
+ "arguments": [
+ "%s",
+ "--total_capacity=%s",
+ "--work_dir=%s"
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ )~",
+ testCsiPluginPath,
+ testCsiPluginPath,
+ stringify(capacity),
+ testCsiPluginWorkDir);
+
+ CHECK_SOME(resourceProviderConfig);
+
+ Try<JSON::Object> json =
+ JSON::parse<JSON::Object>(resourceProviderConfig.get());
+ CHECK_SOME(json);
+
+ Try<ResourceProviderInfo> info =
+ ::protobuf::parse<ResourceProviderInfo>(json.get());
+ CHECK_SOME(info);
+
+ return info.get();
+ }
+
+ Future<http::Response> addResourceProviderConfig(
+ const PID<Slave>& pid,
+ const ContentType& contentType,
+ const ResourceProviderInfo& info)
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ agent::Call call;
+ call.set_type(agent::Call::ADD_RESOURCE_PROVIDER_CONFIG);
+ call.mutable_add_resource_provider_config()
+ ->mutable_info()->CopyFrom(info);
+
+ return http::post(
+ pid,
+ "api/v1",
+ headers,
+ serialize(contentType, evolve(call)),
+ stringify(contentType));
+ }
+
+ Future<http::Response> updateResourceProviderConfig(
+ const PID<Slave>& pid,
+ const ContentType& contentType,
+ const ResourceProviderInfo& info)
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ agent::Call call;
+ call.set_type(agent::Call::UPDATE_RESOURCE_PROVIDER_CONFIG);
+ call.mutable_update_resource_provider_config()
+ ->mutable_info()->CopyFrom(info);
+
+ return http::post(
+ pid,
+ "api/v1",
+ headers,
+ serialize(contentType, evolve(call)),
+ stringify(contentType));
+ }
+
+ Future<http::Response> removeResourceProviderConfig(
+ const PID<Slave>& pid,
+ const ContentType& contentType,
+ const string& type,
+ const string& name)
+ {
+ http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
+ headers["Accept"] = stringify(contentType);
+
+ agent::Call call;
+ call.set_type(agent::Call::REMOVE_RESOURCE_PROVIDER_CONFIG);
+ call.mutable_remove_resource_provider_config()->set_type(type);
+ call.mutable_remove_resource_provider_config()->set_name(name);
+
+ return http::post(
+ pid,
+ "api/v1",
+ headers,
+ serialize(contentType, evolve(call)),
+ stringify(contentType));
+ }
+
+protected:
+ string resourceProviderConfigDir;
+};
+
+
+// The tests are parameterized by the content type of the request.
+INSTANTIATE_TEST_CASE_P(
+ ContentType,
+ AgentResourceProviderConfigApiTest,
+ Values(ContentType::PROTOBUF, ContentType::JSON));
+
+
+// This test adds a new resource provider config on the fly.
+TEST_P(AgentResourceProviderConfigApiTest, ROOT_Add)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/linux";
+
+ // Disable HTTP authentication to simplify resource provider interactions.
+ flags.authenticate_http_readwrite = false;
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ // Register a framework to wait for an offer having the provider
+ // resource.
+ FrameworkInfo framework = DEFAULT_FRAMEWORK_INFO;
+ framework.set_roles(0, "storage");
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, framework, master.get()->pid, DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _));
+
+ Future<vector<Offer>> offers;
+
+ // Decline offers that contain only the agent's default resources.
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillRepeatedly(DeclineOffers());
+
+ EXPECT_CALL(sched, resourceOffers(&driver, OffersHaveAnyResource(
+ std::bind(&Resources::hasResourceProvider, lambda::_1))))
+ .WillOnce(FutureArg<1>(&offers));
+
+ driver.start();
+
+ // Add a new resource provider.
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(4));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::OK().status,
+ addResourceProviderConfig(slave.get()->pid, contentType, info));
+
+ // Check that a new config file is created.
+ Try<list<string>> configPaths =
+ fs::list(path::join(resourceProviderConfigDir, "*"));
+ ASSERT_SOME(configPaths);
+ EXPECT_EQ(1u, configPaths->size());
+
+ Try<string> read = os::read(configPaths->back());
+ ASSERT_SOME(read);
+
+ Try<JSON::Object> json = JSON::parse<JSON::Object>(read.get());
+ ASSERT_SOME(json);
+
+ Try<ResourceProviderInfo> _info =
+ ::protobuf::parse<ResourceProviderInfo>(json.get());
+ ASSERT_SOME(_info);
+ EXPECT_EQ(_info.get(), info);
+
+ // Wait for an offer having the provider resource.
+ AWAIT_READY(offers);
+}
+
+
+// This test checks that adding a resource provider config that already
+// exists is not allowed.
+TEST_P(AgentResourceProviderConfigApiTest, ROOT_AddConflict)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/linux";
+
+ // Disable HTTP authentication to simplify resource provider interactions.
+ flags.authenticate_http_readwrite = false;
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ // Generate a pre-existing config.
+ const string configPath = path::join(resourceProviderConfigDir, "test.json");
+ ASSERT_SOME(os::write(
+ configPath,
+ stringify(JSON::protobuf(createResourceProviderInfo(Gigabytes(4))))));
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(2));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::Conflict().status,
+ addResourceProviderConfig(slave.get()->pid, contentType, info));
+
+ // Check that no new config is created, and the existing one is not
+ // overwritten.
+ Try<list<string>> configPaths =
+ fs::list(path::join(resourceProviderConfigDir, "*"));
+ ASSERT_SOME(configPaths);
+ EXPECT_EQ(1u, configPaths->size());
+ EXPECT_EQ(configPath, configPaths->back());
+
+ Try<string> read = os::read(configPath);
+ ASSERT_SOME(read);
+
+ Try<JSON::Object> json = JSON::parse<JSON::Object>(read.get());
+ ASSERT_SOME(json);
+
+ Try<ResourceProviderInfo> _info =
+ ::protobuf::parse<ResourceProviderInfo>(json.get());
+ ASSERT_SOME(_info);
+ EXPECT_NE(_info.get(), info);
+}
+
+
+// This test updates an existing resource provider config on the fly.
+TEST_P(AgentResourceProviderConfigApiTest, ROOT_Update)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/linux";
+
+ // Disable HTTP authentication to simplify resource provider interactions.
+ flags.authenticate_http_readwrite = false;
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ // Generate a pre-existing config.
+ const string configPath = path::join(resourceProviderConfigDir, "test.json");
+ ASSERT_SOME(os::write(
+ configPath,
+ stringify(JSON::protobuf(createResourceProviderInfo(Gigabytes(4))))));
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ // Register a framework to wait for an offer having the provider
+ // resource.
+ FrameworkInfo framework = DEFAULT_FRAMEWORK_INFO;
+ framework.set_roles(0, "storage");
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, framework, master.get()->pid, DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _));
+
+ Future<vector<Offer>> oldOffers;
+ Future<vector<Offer>> newOffers;
+
+ // Decline offers that contain only the agent's default resources.
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillRepeatedly(DeclineOffers());
+
+ EXPECT_CALL(sched, resourceOffers(&driver, OffersHaveAnyResource(
+ std::bind(&Resources::hasResourceProvider, lambda::_1))))
+ .WillOnce(FutureArg<1>(&oldOffers))
+ .WillOnce(FutureArg<1>(&newOffers));
+
+ Future<OfferID> rescinded;
+
+ EXPECT_CALL(sched, offerRescinded(&driver, _))
+ .WillOnce(FutureArg<1>(&rescinded));
+
+ driver.start();
+
+ // Wait for an offer having the old provider resource.
+ AWAIT_READY(oldOffers);
+
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(2));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::OK().status,
+ updateResourceProviderConfig(slave.get()->pid, contentType, info));
+
+ // Check that no new config is created, and the existing one is overwritten.
+ Try<list<string>> configPaths =
+ fs::list(path::join(resourceProviderConfigDir, "*"));
+ ASSERT_SOME(configPaths);
+ EXPECT_EQ(1u, configPaths->size());
+ EXPECT_EQ(configPath, configPaths->back());
+
+ Try<string> read = os::read(configPath);
+ ASSERT_SOME(read);
+
+ Try<JSON::Object> json = JSON::parse<JSON::Object>(read.get());
+ ASSERT_SOME(json);
+
+ Try<ResourceProviderInfo> _info =
+ ::protobuf::parse<ResourceProviderInfo>(json.get());
+ ASSERT_SOME(_info);
+ EXPECT_EQ(_info.get(), info);
+
+ // Wait for the old offer to be rescinded.
+ AWAIT_READY(rescinded);
+
+ // Wait for an offer having the new provider resource.
+ AWAIT_READY(newOffers);
+
+ // The new provider resource is smaller than the old provider resource.
+ EXPECT_FALSE(Resources(newOffers->at(0).resources()).contains(
+ oldOffers->at(0).resources()));
+}
+
+
+// This test checks that updating a nonexistent resource provider config
+// is not allowed.
+TEST_P(AgentResourceProviderConfigApiTest, UpdateNotFound)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(4));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::NotFound().status,
+ updateResourceProviderConfig(slave.get()->pid, contentType, info));
+
+ // Check that no new config is created.
+ Try<list<string>> configPaths =
+ fs::list(path::join(resourceProviderConfigDir, "*"));
+ ASSERT_SOME(configPaths);
+ EXPECT_TRUE(configPaths->empty());
+}
+
+
+// This test removes an existing resource provider config on the fly.
+TEST_P(AgentResourceProviderConfigApiTest, ROOT_Remove)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+ flags.isolation = "filesystem/linux";
+
+ // Disable HTTP authentication to simplify resource provider interactions.
+ flags.authenticate_http_readwrite = false;
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ // Generate a pre-existing config.
+ const string configPath = path::join(resourceProviderConfigDir, "test.json");
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(4));
+ ASSERT_SOME(os::write(configPath, stringify(JSON::protobuf(info))));
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ // Register a framework to wait for an offer having the provider
+ // resource.
+ FrameworkInfo framework = DEFAULT_FRAMEWORK_INFO;
+ framework.set_roles(0, "storage");
+
+ MockScheduler sched;
+ MesosSchedulerDriver driver(
+ &sched, framework, master.get()->pid, DEFAULT_CREDENTIAL);
+
+ EXPECT_CALL(sched, registered(&driver, _, _));
+
+ Future<vector<Offer>> oldOffers;
+
+ // Decline offers that contain only the agent's default resources.
+ EXPECT_CALL(sched, resourceOffers(&driver, _))
+ .WillRepeatedly(DeclineOffers());
+
+ EXPECT_CALL(sched, resourceOffers(&driver, OffersHaveAnyResource(
+ std::bind(&Resources::hasResourceProvider, lambda::_1))))
+ .WillOnce(FutureArg<1>(&oldOffers));
+
+ // TODO(chhsiao): Wait for an rescinded offer once we implemented the
+ // logic to send `UpdateSlaveMessage` upon removal of a resource
+ // provider.
+
+ driver.start();
+
+ // Wait for an offer having the old provider resource.
+ AWAIT_READY(oldOffers);
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::OK().status,
+ removeResourceProviderConfig(
+ slave.get()->pid, contentType, info.type(), info.name()));
+
+ // Check that the existing config is removed.
+ EXPECT_FALSE(os::exists(configPath));
+
+ // TODO(chhsiao): Wait for the old offer to be rescinded.
+}
+
+
+// This test checks that removing a nonexistent resource provider config
+// is not allowed.
+TEST_P(AgentResourceProviderConfigApiTest, RemoveNotFound)
+{
+ const ContentType contentType = GetParam();
+
+ Try<Owned<cluster::Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Owned<MasterDetector> detector = master.get()->createDetector();
+
+ slave::Flags flags = CreateSlaveFlags();
+
+ // Set the resource provider capability and other required capabilities.
+ constexpr SlaveInfo::Capability::Type capabilities[] = {
+ SlaveInfo::Capability::MULTI_ROLE,
+ SlaveInfo::Capability::HIERARCHICAL_ROLE,
+ SlaveInfo::Capability::RESERVATION_REFINEMENT,
+ SlaveInfo::Capability::RESOURCE_PROVIDER
+ };
+
+ flags.agent_features = SlaveCapabilities();
+ foreach (SlaveInfo::Capability::Type type, capabilities) {
+ flags.agent_features->add_capabilities()->set_type(type);
+ }
+
+ flags.resource_provider_config_dir = resourceProviderConfigDir;
+
+ Future<SlaveRegisteredMessage> slaveRegisteredMessage =
+ FUTURE_PROTOBUF(SlaveRegisteredMessage(), _, _);
+
+ Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), flags);
+ ASSERT_SOME(slave);
+
+ AWAIT_READY(slaveRegisteredMessage);
+
+ ResourceProviderInfo info = createResourceProviderInfo(Gigabytes(4));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+ http::NotFound().status,
+ removeResourceProviderConfig(
+ slave.get()->pid, contentType, info.type(), info.name()));
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {