blob: 3f86573e8dfeea63fe99064f2137c61d901f4fc7 [file] [log] [blame]
// 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 <memory>
#include <string>
#include <gmock/gmock.h>
#include <mesos/executor.hpp>
#include <mesos/v1/mesos.hpp>
#include <mesos/v1/resources.hpp>
#include <mesos/v1/scheduler.hpp>
#include <mesos/v1/scheduler/scheduler.hpp>
#include <process/clock.hpp>
#include <process/future.hpp>
#include <process/gmock.hpp>
#include <process/gtest.hpp>
#include <process/owned.hpp>
#include <process/pid.hpp>
#include <stout/lambda.hpp>
#include <stout/try.hpp>
#include "internal/devolve.hpp"
#include "internal/evolve.hpp"
#include "master/allocator/mesos/allocator.hpp"
#include "master/detector/standalone.hpp"
#include "master/master.hpp"
#include "tests/mesos.hpp"
#include "tests/master/mock_master_api_subscriber.hpp"
using mesos::internal::master::Master;
using mesos::internal::master::allocator::MesosAllocatorProcess;
using mesos::internal::recordio::Reader;
using mesos::internal::slave::Containerizer;
using mesos::internal::slave::Slave;
using mesos::master::detector::MasterDetector;
using mesos::v1::FrameworkInfo;
using mesos::v1::scheduler::APIResult;
using mesos::v1::scheduler::Call;
using mesos::v1::scheduler::Event;
using mesos::v1::scheduler::Mesos;
using process::Clock;
using process::Failure;
using process::Future;
using process::Owned;
using process::PID;
using process::Promise;
using recordio::Decoder;
using google::protobuf::RepeatedPtrField;
using std::string;
using std::vector;
using testing::_;
using testing::AtMost;
using testing::Return;
namespace mesos {
namespace internal {
namespace tests {
template <class TFrameworkInfo>
static TFrameworkInfo changeAllMutableFields(const TFrameworkInfo& oldInfo)
{
CHECK_EQ(TFrameworkInfo::descriptor()->field_count(), 13)
<< "After adding a new mutable field to FrameworkInfo, please make sure "
<< "that this function modifies this field";
TFrameworkInfo newInfo = oldInfo;
*newInfo.mutable_name() += "_foo";
newInfo.set_failover_timeout(newInfo.failover_timeout() + 1000.0);
*newInfo.mutable_hostname() += ".foo";
*newInfo.mutable_webui_url() += "/foo";
newInfo.add_capabilities()->set_type(
TFrameworkInfo::Capability::REGION_AWARE);
auto* newLabel = newInfo.mutable_labels()->add_labels();
*newLabel->mutable_key() = "UPDATE_FRAMEWORK_KEY";
*newLabel->mutable_value() = "UPDATE_FRAMEWORK_VALUE";
// TODO(asekretenko): Test update of `role` with a non-MULTI_ROLE framework.
newInfo.add_roles("new_role");
CHECK(newInfo.offer_filters().count("new_role") == 0);
(*newInfo.mutable_offer_filters())["new_role"] =
typename std::remove_reference<decltype(
newInfo.offer_filters())>::type::mapped_type();
return newInfo;
}
namespace v1 {
static Future<v1::master::Response::GetFrameworks> getFrameworks(
const process::PID<Master>& pid)
{
v1::master::Call call;
call.set_type(v1::master::Call::GET_FRAMEWORKS);
process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL);
headers["Accept"] = stringify(ContentType::PROTOBUF);
return process::http::post(
pid,
"api/v1",
headers,
serialize(ContentType::PROTOBUF, call),
stringify(ContentType::PROTOBUF))
.then([](const process::http::Response& httpResponse)
-> Future<v1::master::Response::GetFrameworks> {
if (httpResponse.status != process::http::OK().status) {
return Failure(
"GET_FRAMEWORKS failed with response status " +
httpResponse.status);
}
Try<v1::master::Response> response =
deserialize<mesos::v1::master::Response>(
ContentType::PROTOBUF, httpResponse.body);
if (response.isError()) {
return Failure(response.error());
}
if (!response->has_get_frameworks()) {
return Failure("Response to GET_FRAMEWORKS has no 'get_frameworks'");
}
return response->get_frameworks();
});
}
namespace scheduler {
class UpdateFrameworkTest : public MesosTest {};
static Future<APIResult> callUpdateFramework(
Mesos* mesos,
const FrameworkInfo& info,
const vector<string>& suppressedRoles = {},
const Option<::mesos::v1::scheduler::OfferConstraints>& offerConstraints =
None())
{
CHECK(info.has_id());
Call call;
call.set_type(Call::UPDATE_FRAMEWORK);
*call.mutable_framework_id() = info.id();
*call.mutable_update_framework()->mutable_framework_info() = info;
*call.mutable_update_framework()->mutable_suppressed_roles() =
RepeatedPtrField<string>(suppressedRoles.begin(), suppressedRoles.end());
if (offerConstraints.isSome()) {
*call.mutable_update_framework()->mutable_offer_constraints() =
*offerConstraints;
}
return mesos->call(call);
}
TEST_F(UpdateFrameworkTest, UserChangeFails)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
*update.mutable_id() = subscribed->framework_id();
*update.mutable_user() += "_foo";
Future<APIResult> result = callUpdateFramework(&mesos, update);
AWAIT_READY(result);
EXPECT_EQ(result->status_code(), 400u);
EXPECT_TRUE(strings::contains(
result->error(), "Updating 'FrameworkInfo.user' is unsupported"));
// Check that no partial update occurred.
Future<v1::master::Response::GetFrameworks> frameworks =
getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
FrameworkInfo expected = DEFAULT_FRAMEWORK_INFO;
*expected.mutable_id() = subscribed->framework_id();
EXPECT_NONE(::mesos::v1::typeutils::diff(
frameworks->frameworks(0).framework_info(), expected));
// Sanity check for diff()
EXPECT_SOME(::mesos::v1::typeutils::diff(update, expected));
}
TEST_F(UpdateFrameworkTest, PrincipalChangeFails)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
*update.mutable_id() = subscribed->framework_id();
*update.mutable_principal() += "_foo";
Future<APIResult> result = callUpdateFramework(&mesos, update);
AWAIT_READY(result);
EXPECT_EQ(result->status_code(), 400u);
EXPECT_TRUE(strings::contains(
result->error(), "Changing framework's principal is not allowed"));
// Check that no partial update occurred.
Future<v1::master::Response::GetFrameworks> frameworks =
getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
FrameworkInfo expected = DEFAULT_FRAMEWORK_INFO;
*expected.mutable_id() = subscribed->framework_id();
EXPECT_NONE(::mesos::v1::typeutils::diff(
frameworks->frameworks(0).framework_info(), expected));
// Sanity check for diff()
EXPECT_SOME(::mesos::v1::typeutils::diff(update, expected));
}
TEST_F(UpdateFrameworkTest, CheckpointingChangeFails)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
*update.mutable_id() = subscribed->framework_id();
update.set_checkpoint(!update.checkpoint());
Future<APIResult> result = callUpdateFramework(&mesos, update);
AWAIT_READY(result);
EXPECT_EQ(result->status_code(), 400u);
EXPECT_TRUE(strings::contains(
result->error(), "Updating 'FrameworkInfo.checkpoint' is unsupported"));
// Check that no partial update occurred.
Future<v1::master::Response::GetFrameworks> frameworks =
getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
FrameworkInfo expected = DEFAULT_FRAMEWORK_INFO;
*expected.mutable_id() = subscribed->framework_id();
EXPECT_NONE(::mesos::v1::typeutils::diff(
frameworks->frameworks(0).framework_info(), expected));
// Sanity check for diff()
EXPECT_SOME(::mesos::v1::typeutils::diff(update, expected));
}
// TODO(asekretenko): Add more tests for invalid updates:
// - Try to add a nonexisting role.
// - Try to add a role which the framework is not authorized to use.
// ....
// This test checks that it is possible to update all the mutable fields.
// It verifies the following:
// - HTTP status code of the scheduler API response.
// - FrameworkInfo returned by GetFrameworks API call.
// - FrameworkInfo sent in UpdateFrameworkMessage to the slave.
// - FrameworkInfo sent in FRAMEWORK_UPDATED to the API subscribers.
TEST_F(UpdateFrameworkTest, MutableFieldsUpdateSuccessfully)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
// Subscribe to master v1 API.
MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> agentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(FutureSatisfy(&agentAdded));
// We need a slave to test the UpdateFrameworkMessage.
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
// To test the UpdateFrameworkMessage, we should wait for the slave
// to be added before calling UPDATE_FRAMEWORK.
AWAIT_READY(agentAdded);
// Expect FRAMEWORK_UPDATED event
Future<v1::master::Event::FrameworkUpdated> frameworkUpdated;
EXPECT_CALL(masterAPISubscriber, frameworkUpdated(_))
.WillOnce(FutureArg<0>(&frameworkUpdated));
// Expect UpdateFrameworkMessage to be sent from master to slave.
Future<UpdateFrameworkMessage> updateFrameworkMessage = FUTURE_PROTOBUF(
UpdateFrameworkMessage(), master->get()->pid, slave->get()->pid);
// Start scheduler, wait for connection and subscribe
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
*update.mutable_id() = subscribed->framework_id();
Future<APIResult> result = callUpdateFramework(&mesos, update);
AWAIT_READY(result);
EXPECT_EQ(result->status_code(), 200u);
// Check that update occurred.
Future<v1::master::Response::GetFrameworks> frameworks =
getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
const FrameworkInfo& frameworkInfo =
frameworks->frameworks(0).framework_info();
EXPECT_NONE(::mesos::v1::typeutils::diff(frameworkInfo, update));
AWAIT_READY(updateFrameworkMessage);
EXPECT_NONE(::mesos::v1::typeutils::diff(
evolve(updateFrameworkMessage->framework_info()), update));
AWAIT_READY(frameworkUpdated);
EXPECT_NONE(::mesos::v1::typeutils::diff(
frameworkUpdated->framework().framework_info(), update));
};
// This test issues two UpdateFrameworkCalls: the first one with the same
// `FrameworkInfo`, the second with mutated `FrameworkInfo`,
// and verifies that the first call does NOT result in updates
// to agents/subscribers.
TEST_F(UpdateFrameworkTest, NoRedundantUpdates)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
// Subscribe to master v1 API.
MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> agentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(FutureSatisfy(&agentAdded));
// We need an agent to test the UpdateFrameworkMessage.
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
// To test the UpdateFrameworkMessage, we should wait for the agent
// to be added before calling UPDATE_FRAMEWORK.
AWAIT_READY(agentAdded);
// Expect a single FRAMEWORK_UPDATED event.
Future<v1::master::Event::FrameworkUpdated> frameworkUpdated;
EXPECT_CALL(masterAPISubscriber, frameworkUpdated(_))
.WillOnce(FutureArg<0>(&frameworkUpdated));
// Expect UpdateFrameworkMessage to be sent from the master to the agent.
Future<UpdateFrameworkMessage> updateFrameworkMessage = FUTURE_PROTOBUF(
UpdateFrameworkMessage(), master->get()->pid, slave->get()->pid);
// Start the scheduler, wait for connection and then subscribe.
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
// To send UPDATE_FRAMEWORK, we need to obtain a framework ID.
AWAIT_READY(subscribed);
// Issue an UPDATE_FRAMEWORK that does not touch `FrameworkInfo`.
FrameworkInfo update1 = DEFAULT_FRAMEWORK_INFO;
*update1.mutable_id() = subscribed->framework_id();
Future<APIResult> result1 = callUpdateFramework(&mesos, update1);
AWAIT_READY(result1);
ASSERT_EQ(result1->status_code(), 200u);
// Verify that the first update has not resulted in broadcasts to
// agents/subscribers.
Clock::pause();
Clock::settle();
ASSERT_TRUE(frameworkUpdated.isPending());
ASSERT_TRUE(updateFrameworkMessage.isPending());
// Change `FrameworkInfo` via UPDATE_FRAMEWORK.
const FrameworkInfo update2 = changeAllMutableFields(update1);
Future<APIResult> result2 = callUpdateFramework(&mesos, update2);
AWAIT_READY(result2);
EXPECT_EQ(result2->status_code(), 200u);
// Verify that the broadcasts report the second update.
AWAIT_READY(updateFrameworkMessage);
EXPECT_NONE(::mesos::v1::typeutils::diff(
evolve(updateFrameworkMessage->framework_info()), update2));
AWAIT_READY(frameworkUpdated);
EXPECT_NONE(::mesos::v1::typeutils::diff(
frameworkUpdated->framework().framework_info(), update2));
};
// This tests that adding a role via UPDATE_FRAMEWORK to a framework which had
// no roles triggers allocation of an offer for that role.
TEST_F(UpdateFrameworkTest, OffersOnAddingRole)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
// There are at least two distinct cases that one might want to test:
// - That adding a role triggers allocation.
// - That adding a slave triggers allocation when the framework has roles.
//
// In this test the intention is to test the first case - and definitely
// not to alternate between these two cases from run to run.
// Therefore, before making scheduler calls, we need to wait for the slave to
// be added. This is done by waiting for an AGENT_ADDED master API event.
MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> agentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(FutureSatisfy(&agentAdded));
Owned<MasterDetector> detector = master->get()->createDetector();
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
AWAIT_READY(agentAdded);
auto scheduler = std::make_shared<MockHTTPScheduler>();
// Initially, the framework subscribes with no roles.
FrameworkInfo initialFrameworkInfo = DEFAULT_FRAMEWORK_INFO;
initialFrameworkInfo.clear_roles();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(initialFrameworkInfo));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
// Expect that the framework gets no offers before update.
EXPECT_CALL(*scheduler, offers(_, _))
.Times(AtMost(0));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
// Trigger allocation to ensure that offers are not generated before update.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
// Expect an offer after adding a role.
Future<Event::Offers> offers;
EXPECT_CALL(*scheduler, offers(_, _))
.WillOnce(FutureArg<1>(&offers));
FrameworkInfo update = initialFrameworkInfo;
*update.mutable_id() = subscribed->framework_id();
update.add_roles("new_role");
// To spare test running time, we wait for the update response and advance
// clock after that.
AWAIT_READY(callUpdateFramework(&mesos, update));
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
AWAIT_READY(offers);
ASSERT_EQ(offers->offers().size(), 1);
EXPECT_EQ(offers->offers(0).allocation_info().role(), "new_role");
}
// Test that framework's offers are rescinded when a framework is
// removed from all its roles via UPDATE_FRAMEWORK.
TEST_F(UpdateFrameworkTest, RescindOnRemovingRoles)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
auto scheduler = std::make_shared<MockHTTPScheduler>();
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
// Expect offers. This should happen exactly once (when both
// the slave is added and the framework is subscribed).
Future<Event::Offers> offers;
EXPECT_CALL(*scheduler, offers(_, _))
.WillOnce(FutureArg<1>(&offers));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(offers);
ASSERT_EQ(offers->offers().size(), 1);
// Set up expectations for things that should happen after role removal.
// The offer for the removed role should be rescinded.
Future<Event::Rescind> rescind;
EXPECT_CALL(*scheduler, rescind(_, _))
.WillOnce(FutureArg<1>(&rescind));
// recoverResources() should be called.
//
// TODO(asekretenko): Add a more in-depth check that the
// allocator does what it should.
Future<Nothing> recoverResources =
FUTURE_DISPATCH(_, &MesosAllocatorProcess::recoverResources);
// Remove the framework from the role via UPDATE_FRAMEWORK.
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
*update.mutable_id() = subscribed->framework_id();
update.clear_roles();
callUpdateFramework(&mesos, update);
AWAIT_READY(rescind);
AWAIT_READY(recoverResources);
EXPECT_EQ(offers->offers(0).id(), rescind->offer_id());
// After that, nothing of interest should happen within an allocation
// interval: no more offers and no more rescinding.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
}
// This test ensures that it is possible to add
// a suppressed role via UPDATE_FRAMEWORK.
TEST_F(UpdateFrameworkTest, AddSuppressedRole)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
auto scheduler = std::make_shared<MockHTTPScheduler>();
// Initially, the framework subscribes with no roles.
FrameworkInfo initialFrameworkInfo = DEFAULT_FRAMEWORK_INFO;
initialFrameworkInfo.clear_roles();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribe(initialFrameworkInfo));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
// Expect that the framework gets no offers before update.
EXPECT_CALL(*scheduler, offers(_, _))
.Times(AtMost(0));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
// Add a suppressed role.
FrameworkInfo update = initialFrameworkInfo;
*update.mutable_id() = subscribed->framework_id();
update.add_roles("new_role");
AWAIT_READY(callUpdateFramework(&mesos, update, {"new_role"}));
// Trigger allocation to ensure that offers are not generated.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
}
// Helper action for RemoveAndUnsuppress.
ACTION_P(SendSubscribeWithAllRolesSuppressed, framework)
{
Call call;
call.set_type(Call::SUBSCRIBE);
*call.mutable_subscribe()->mutable_framework_info() = framework;
*call.mutable_subscribe()->mutable_suppressed_roles() = framework.roles();
arg0->send(call);
}
// This test ensures that it is possible to remove roles both from the roles
// set and the suppressed roles set.
TEST_F(UpdateFrameworkTest, RemoveAndUnsuppress)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
auto scheduler = std::make_shared<MockHTTPScheduler>();
Future<Nothing> connected;
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(SendSubscribeWithAllRolesSuppressed(DEFAULT_FRAMEWORK_INFO));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _))
.WillOnce(FutureArg<1>(&subscribed));
// Expect that the framework gets no offers.
EXPECT_CALL(*scheduler, offers(_, _))
.Times(AtMost(0));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
// Remove suppressed roles while unsuppressing them.
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
*update.mutable_id() = subscribed->framework_id();
update.clear_roles();
AWAIT_READY(callUpdateFramework(&mesos, update, {}));
// Trigger allocation to ensure that nothing happens.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
}
// This test ensures that it is possible to modify offer constraints
// via the UpdateFramework call.
TEST_F(UpdateFrameworkTest, OfferConstraints)
{
using ::mesos::v1::scheduler::AttributeConstraint;
using ::mesos::v1::scheduler::OfferConstraints;
const Try<JSON::Object> initialConstraintsJson =
JSON::parse<JSON::Object>(R"~(
{
"role_constraints": {
")~" + DEFAULT_FRAMEWORK_INFO.roles(0) + R"~(": {
"groups": [{
"attribute_constraints": [{
"selector": {"attribute_name": "foo"},
"predicate": {"exists": {}}
}]
}]
}
}
})~");
ASSERT_SOME(initialConstraintsJson);
const Try<OfferConstraints> initialConstraints =
::protobuf::parse<OfferConstraints>(*initialConstraintsJson);
ASSERT_SOME(initialConstraints);
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
mesos::internal::slave::Flags slaveFlags = CreateSlaveFlags();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags);
ASSERT_SOME(slave);
auto scheduler = std::make_shared<MockHTTPScheduler>();
EXPECT_CALL(*scheduler, connected(_))
.WillOnce(Invoke([initialConstraints](Mesos* mesos) {
Call call;
call.set_type(Call::SUBSCRIBE);
*call.mutable_subscribe()->mutable_framework_info() =
DEFAULT_FRAMEWORK_INFO;
*call.mutable_subscribe()->mutable_offer_constraints() =
*initialConstraints;
mesos->send(call);
}));
EXPECT_CALL(*scheduler, heartbeat(_))
.WillRepeatedly(Return()); // Ignore heartbeats.
Future<Event::Subscribed> subscribed;
EXPECT_CALL(*scheduler, subscribed(_, _)).WillOnce(FutureArg<1>(&subscribed));
// Expect that the framework gets no offers.
EXPECT_CALL(*scheduler, offers(_, _)).Times(AtMost(0));
TestMesos mesos(master->get()->pid, ContentType::PROTOBUF, scheduler);
AWAIT_READY(subscribed);
// Trigger allocation to ensure that the agent is not offered before changing
// offer constraints.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
// Expect an offer after constraints change.
Future<Event::Offers> offers;
EXPECT_CALL(*scheduler, offers(_, _)).WillOnce(FutureArg<1>(&offers));
// Change constraint to `NotExists` so that the agent will now be offered to
// the framework.
const Try<JSON::Object> updatedConstraintsJson =
JSON::parse<JSON::Object>(R"~(
{
"role_constraints": {
")~" + DEFAULT_FRAMEWORK_INFO.roles(0) + R"~(": {
"groups": [{
"attribute_constraints": [{
"selector": {"attribute_name": "foo"},
"predicate": {"not_exists": {}}
}]
}]
}
}
})~");
ASSERT_SOME(updatedConstraintsJson);
const Try<OfferConstraints> updatedConstraints =
::protobuf::parse<OfferConstraints>(*updatedConstraintsJson);
ASSERT_SOME(updatedConstraints);
{
FrameworkInfo framework = DEFAULT_FRAMEWORK_INFO;
*framework.mutable_id() = subscribed->framework_id();
AWAIT_READY(
callUpdateFramework(&mesos, framework, {}, *updatedConstraints));
}
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
AWAIT_READY(offers);
EXPECT_EQ(offers->offers().size(), 1);
// Ensure that the updated offer constraints are reflected in the master's
// '/state' response.
{
Future<process::http::Response> response = process::http::get(
master->get()->pid,
"state",
None(),
createBasicAuthHeaders(DEFAULT_CREDENTIAL));
AWAIT_ASSERT_RESPONSE_STATUS_EQ(process::http::OK().status, response);
AWAIT_ASSERT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body);
ASSERT_SOME(parse);
Result<JSON::Object> reportedConstraintsJson = parse->find<JSON::Object>(
"frameworks[0].offer_constraints");
EXPECT_SOME_EQ(*updatedConstraintsJson, reportedConstraintsJson);
}
// Ensure that the updated offer constraints are reflected in the master's
// '/frameworks' response.
{
Future<process::http::Response> response = process::http::get(
master->get()->pid,
"frameworks",
None(),
createBasicAuthHeaders(DEFAULT_CREDENTIAL));
AWAIT_ASSERT_RESPONSE_STATUS_EQ(process::http::OK().status, response);
AWAIT_ASSERT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body);
ASSERT_SOME(parse);
Result<JSON::Object> reportedConstraintsJson = parse->find<JSON::Object>(
"frameworks[0].offer_constraints");
ASSERT_SOME_EQ(*updatedConstraintsJson, reportedConstraintsJson);
}
Future<v1::master::Response::GetFrameworks> frameworks =
getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
ASSERT_TRUE(frameworks->frameworks(0).has_offer_constraints());
// TODO(asekretenko): As the semantics of the offer constraints does not
// depend on the order of constraints groups and the order of individual
// constraints inside groups, we should consider using a `MessageDifferencer`
// configured to take this into account.
ASSERT_EQ(
updatedConstraints->SerializeAsString(),
frameworks->frameworks(0).offer_constraints().SerializeAsString());
}
} // namespace scheduler {
} // namespace v1 {
// Base class for tests of V0 UPDATE_FRAMEWORK call
class UpdateFrameworkV0Test : public MesosTest {};
TEST_F(UpdateFrameworkV0Test, DriverErrorWhenCalledBeforeRegistration)
{
::mesos::master::detector::StandaloneMasterDetector detector;
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, &detector, DEFAULT_FRAMEWORK_INFO);
Future<string> error;
EXPECT_CALL(sched, error(&driver, _))
.WillOnce(FutureArg<1>(&error));
driver.start();
driver.updateFramework(DEFAULT_FRAMEWORK_INFO, {}, {});
AWAIT_READY(error);
EXPECT_EQ(error.get(),
"MesosSchedulerDriver::updateFramework() must not be called"
" prior to registration with the master");
driver.stop();
driver.join();
}
TEST_F(UpdateFrameworkV0Test, DriverErrorOnFrameworkIDMismatch)
{
Try<Owned<cluster::Master>> master = StartMaster();
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
Future<string> error;
EXPECT_CALL(sched, error(&driver, _))
.WillOnce(FutureArg<1>(&error));
driver.start();
AWAIT_READY(frameworkId);
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
*update.mutable_id() = frameworkId.get();
*update.mutable_id()->mutable_value() += "-deadbeef";
driver.updateFramework(update, {}, {});
AWAIT_READY(error);
EXPECT_EQ(
error.get(),
"The 'FrameworkInfo.id' provided to"
" MesosSchedulerDriver::updateFramework()"
" (" + stringify(update.id()) + ")"
" must be equal to the value known to the MesosSchedulerDriver"
" (" + stringify(frameworkId.get()) + ")");
driver.stop();
driver.join();
}
TEST_F(UpdateFrameworkV0Test, CheckpointingChangeFails)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
Future<string> error;
EXPECT_CALL(sched, error(&driver, _))
.WillOnce(FutureArg<1>(&error));
driver.start();
AWAIT_READY(frameworkId);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
update.set_checkpoint(!update.checkpoint());
*update.mutable_id() = frameworkId.get();
driver.updateFramework(update, {}, {});
AWAIT_READY(error);
EXPECT_TRUE(strings::contains(
error.get(), "Updating 'FrameworkInfo.checkpoint' is unsupported"));
driver.stop();
driver.join();
}
TEST_F(UpdateFrameworkV0Test, MutableFieldsUpdateSuccessfully)
{
Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
// Subscribe to master v1 API.
v1::MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> agentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(FutureSatisfy(&agentAdded));
// We need a slave to test the UpdateFrameworkMessage.
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
ASSERT_SOME(slave);
// To test the UpdateFrameworkMessage, we should wait for the slave
// to be added before calling UPDATE_FRAMEWORK.
AWAIT_READY(agentAdded);
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
driver.start();
AWAIT_READY(frameworkId);
// Expect FRAMEWORK_UPDATED event after update.
Future<v1::master::Event::FrameworkUpdated> frameworkUpdated;
EXPECT_CALL(masterAPISubscriber, frameworkUpdated(_))
.WillOnce(FutureArg<0>(&frameworkUpdated));
// Expect UpdateFrameworkMessage to be sent from master to slave.
Future<UpdateFrameworkMessage> updateFrameworkMessage = FUTURE_PROTOBUF(
UpdateFrameworkMessage(), master->get()->pid, slave->get()->pid);
FrameworkInfo update = changeAllMutableFields(DEFAULT_FRAMEWORK_INFO);
*update.mutable_id() = frameworkId.get();
driver.updateFramework(update, {}, {});
AWAIT_READY(updateFrameworkMessage);
EXPECT_NONE(::mesos::typeutils::diff(
updateFrameworkMessage->framework_info(), update));
AWAIT_READY(frameworkUpdated);
EXPECT_NONE(::mesos::typeutils::diff(
devolve(frameworkUpdated->framework().framework_info()), update));
Future<v1::master::Response::GetFrameworks> frameworks =
v1::getFrameworks(master->get()->pid);
AWAIT_READY(frameworks);
ASSERT_EQ(frameworks->frameworks_size(), 1);
const FrameworkInfo& reportedFrameworkInfo =
devolve(frameworks->frameworks(0).framework_info());
EXPECT_NONE(::mesos::typeutils::diff(reportedFrameworkInfo, update));
driver.stop();
driver.join();
}
// This tests that adding a role via UPDATE_FRAMEWORK to a framework which had
// no roles triggers allocation of an offer for that role.
TEST_F(UpdateFrameworkV0Test, OffersOnAddingRole)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
// There are at least two distinct cases that one might want to test:
// - That adding a role triggers allocation.
// - That adding a slave triggers allocation when the framework has roles.
//
// In this test the intention is to test the first case - and definitely
// not to alternate between these two cases from run to run.
// Therefore, before making scheduler calls, we need to wait for the slave to
// be added. This is done by waiting for an AGENT_ADDED master API event.
v1::MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> agentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(FutureSatisfy(&agentAdded));
Owned<MasterDetector> detector = master->get()->createDetector();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
ASSERT_SOME(slave);
AWAIT_READY(agentAdded);
// Subscribe without roles.
FrameworkInfo initialFrameworkInfo = DEFAULT_FRAMEWORK_INFO;
initialFrameworkInfo.clear_roles();
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), initialFrameworkInfo);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
// Check that the framework gets no offers before update.
EXPECT_CALL(sched, resourceOffers(&driver, _))
.Times(AtMost(0));
driver.start();
AWAIT_READY(frameworkId);
// Trigger allocation to ensure that offers are not generated before update.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
Clock::resume();
// Expect an offer after adding a role.
Future<std::vector<Offer>> offers;
EXPECT_CALL(sched, resourceOffers(_, _))
.WillOnce(FutureArg<1>(&offers));
// Add a role via update and wait for offers.
FrameworkInfo update = initialFrameworkInfo;
update.clear_roles();
update.add_roles("new_role");
*update.mutable_id() = frameworkId.get();
driver.updateFramework(update, {}, {});
AWAIT_READY(offers);
ASSERT_EQ(offers->size(), 1u);
EXPECT_EQ(offers->front().allocation_info().role(), "new_role");
}
// Test that framework's offers are rescinded when a framework is
// removed from all its roles via UPDATE_FRAMEWORK.
TEST_F(UpdateFrameworkV0Test, RescindOnRemovingRoles)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
ASSERT_SOME(slave);
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
// Expect an offer exactly once (after subscribing).
Future<std::vector<Offer>> offers;
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
driver.start();
AWAIT_READY(frameworkId);
AWAIT_READY(offers);
ASSERT_EQ(offers->size(), 1u);
// Set up expectations for things that should happen after role removal.
// The offer for the removed role should be rescinded.
Future<OfferID> rescindedOfferId;
EXPECT_CALL(sched, offerRescinded(_, _))
.WillOnce(FutureArg<1>(&rescindedOfferId));
// recoverResources() should be called.
//
// TODO(asekretenko): Add a more in-depth check that
// the allocator does what it should.
Future<Nothing> recoverResources =
FUTURE_DISPATCH(_, &MesosAllocatorProcess::recoverResources);
// Remove the framework from all roles via update.
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
update.clear_roles();
*update.mutable_id() = frameworkId.get();
driver.updateFramework(update, {}, {});
AWAIT_READY(rescindedOfferId);
AWAIT_READY(recoverResources);
EXPECT_EQ(offers->front().id(), rescindedOfferId.get());
// After that, nothing of interest should happen within an allocation
// interval: no more offers and no more rescinding.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
driver.stop();
driver.join();
}
// Test that framework can suppress roles via updateFramework():
// - start a master, a driver and a slave
// - wait for offer
// - update suppressed roles via updateFramework
// - add a new slave and make sure we get no offers after that.
TEST_F(UpdateFrameworkV0Test, SuppressedRoles)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
v1::MockMasterAPISubscriber masterAPISubscriber;
AWAIT_READY(masterAPISubscriber.subscribe(master.get()->pid));
Future<Nothing> secondAgentAdded;
EXPECT_CALL(masterAPISubscriber, agentAdded(_))
.WillOnce(Return())
.WillOnce(FutureSatisfy(&secondAgentAdded));
Owned<MasterDetector> detector = master->get()->createDetector();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
ASSERT_SOME(slave);
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
// Expect an offer EXACTLY once (after subscribing and before adding
// the second slave).
Future<vector<Offer>> offers;
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
driver.start();
AWAIT_READY(frameworkId);
AWAIT_READY(offers);
ASSERT_EQ(1u, offers->size());
// Suppress all roles via UPDATE_FRAMEWORK.
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
*update.mutable_id() = frameworkId.get();
vector<string> suppressedRoles(
update.roles().begin(), update.roles().end());
driver.updateFramework(update, suppressedRoles, {});
// Ensure that the allocator processes the update, so that this test
// does not rely on Master maintaining an ordering between scheduler API calls
// processing and agent registration.
Clock::pause();
Clock::settle();
Clock::resume();
Try<Owned<cluster::Slave>> newSlave = StartSlave(detector.get());
ASSERT_SOME(newSlave);
AWAIT_READY(secondAgentAdded);
// After the agent has been added, no offers should be generated
// within an allocation interval.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
Clock::settle();
driver.stop();
driver.join();
}
// Test that when a framework removes a role from the
// suppressed roles, it clears filters (same as REVIVE):
// - start a master, a driver and an agent
// - wait for offer and decline it for a long timeout
// - add / remove suppressed roles via updateFramework
// - ensure we get an offer for the agent again
TEST_F(UpdateFrameworkV0Test, UnsuppressClearsFilters)
{
mesos::internal::master::Flags masterFlags = CreateMasterFlags();
Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Owned<MasterDetector> detector = master->get()->createDetector();
Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
ASSERT_SOME(slave);
MockScheduler sched;
TestingMesosSchedulerDriver driver(
&sched, detector.get(), DEFAULT_FRAMEWORK_INFO);
Future<FrameworkID> frameworkId;
EXPECT_CALL(sched, registered(&driver, _, _))
.WillOnce(FutureArg<1>(&frameworkId));
Future<vector<Offer>> offers1;
Future<vector<Offer>> offers2;
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers1))
.WillOnce(FutureArg<1>(&offers2));
driver.start();
AWAIT_READY(frameworkId);
AWAIT_READY(offers1);
ASSERT_EQ(1u, offers1->size());
Filters filters;
filters.set_refuse_seconds(Days(1).secs());
driver.declineOffer(offers1->at(0).id(), filters);
// Suppress and unsuppress the role.
FrameworkInfo update = DEFAULT_FRAMEWORK_INFO;
*update.mutable_id() = frameworkId.get();
vector<string> suppressedRoles(
update.roles().begin(), update.roles().end());
driver.updateFramework(update, suppressedRoles, {});
driver.updateFramework(update, {}, {});
// Now the previously declined agent should be re-offered.
Clock::pause();
Clock::settle();
Clock::advance(masterFlags.allocation_interval);
AWAIT_READY(offers2);
driver.stop();
driver.join();
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {