Added validation that offer constraints are set only for existing roles.
This patch makes SUBSCRIBE/UPDATE_FRAMEWORK calls validate that
the framework does not specify offer constraints for roles to which
it is not going to be subscribed.
Review: https://reviews.apache.org/r/72956
diff --git a/src/master/master.cpp b/src/master/master.cpp
index 531b971..164720a 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -2619,7 +2619,13 @@
return *error;
}
- // TODO(asekretenko): Validate roles in offer constraints (see MESOS-10176).
+ error = validation::framework::validateOfferConstraintsRoles(
+ validFrameworkRoles, offerConstraints);
+
+ if (error.isSome()) {
+ return *error;
+ }
+
Try<OfferConstraintsFilter> filter = OfferConstraintsFilter::create(
filterOptions, std::move(offerConstraints));
diff --git a/src/master/validation.cpp b/src/master/validation.cpp
index aafffd5..6bdab54 100644
--- a/src/master/validation.cpp
+++ b/src/master/validation.cpp
@@ -59,6 +59,8 @@
using google::protobuf::RepeatedPtrField;
+using mesos::scheduler::OfferConstraints;
+
namespace mesos {
namespace internal {
namespace master {
@@ -697,6 +699,23 @@
}
+Option<Error> validateOfferConstraintsRoles(
+ const set<string>& validFrameworkRoles,
+ const OfferConstraints& offerConstraints)
+{
+ for (const auto& pair : offerConstraints.role_constraints()) {
+ const string& role = pair.first;
+ if (validFrameworkRoles.count(role) < 1) {
+ return Error(
+ "Offer constraints specify `role_constraints` for a role '" + role +
+ "'not contained in the set of roles");
+ }
+ }
+
+ return None();
+}
+
+
} // namespace framework {
diff --git a/src/master/validation.hpp b/src/master/validation.hpp
index 6239492..17652e3 100644
--- a/src/master/validation.hpp
+++ b/src/master/validation.hpp
@@ -134,6 +134,10 @@
const std::set<std::string>& validFrameworkRoles,
const std::set<std::string>& suppressedRoles);
+Option<Error> validateOfferConstraintsRoles(
+ const std::set<std::string>& validFrameworkRoles,
+ const scheduler::OfferConstraints& offerConstraints);
+
} // namespace framework {
diff --git a/src/tests/master/update_framework_tests.cpp b/src/tests/master/update_framework_tests.cpp
index 3f86573..ce5a51c 100644
--- a/src/tests/master/update_framework_tests.cpp
+++ b/src/tests/master/update_framework_tests.cpp
@@ -981,6 +981,93 @@
}
+// This test ensures that an UPDATE_FRAMEWORK call trying to set offer
+// constraints for a role to which the framework will not be subscribed
+// fails validation and is rejected as a whole.
+TEST_F(UpdateFrameworkTest, OfferConstraintsForUnsubscribedRole)
+{
+ using ::mesos::v1::scheduler::OfferConstraints;
+
+ const Try<JSON::Object> constraintsJson = JSON::parse<JSON::Object>(
+ R"~(
+ {
+ "role_constraints": {
+ ")~" + DEFAULT_FRAMEWORK_INFO.roles(0) + R"~(": {
+ "groups": [{
+ "attribute_constraints": [{
+ "selector": {"attribute_name": "foo"},
+ "predicate": {"exists": {}}
+ }]
+ }]
+ }
+ }
+ })~");
+
+ ASSERT_SOME(constraintsJson);
+
+ const Try<OfferConstraints> constraints =
+ ::protobuf::parse<OfferConstraints>(*constraintsJson);
+
+ ASSERT_SOME(constraints);
+
+ Try<Owned<cluster::Master>> master = StartMaster(CreateMasterFlags());
+ ASSERT_SOME(master);
+
+ auto scheduler = std::make_shared<MockHTTPScheduler>();
+
+ Future<Nothing> connected;
+ EXPECT_CALL(*scheduler, connected(_))
+ .WillOnce(Invoke([constraints](Mesos* mesos) {
+ Call call;
+ call.set_type(Call::SUBSCRIBE);
+ *call.mutable_subscribe()->mutable_framework_info() =
+ DEFAULT_FRAMEWORK_INFO;
+
+ *call.mutable_subscribe()->mutable_offer_constraints() = *constraints;
+
+ mesos->send(call);
+ }));
+
+ 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);
+
+ // Try to change framework's role but specify constraints for the old role.
+ FrameworkInfo frameworkInfoWithNewRole = DEFAULT_FRAMEWORK_INFO;
+ frameworkInfoWithNewRole.clear_roles();
+ frameworkInfoWithNewRole.add_roles(DEFAULT_FRAMEWORK_INFO.roles(0) + "_new");
+ *frameworkInfoWithNewRole.mutable_id() = subscribed->framework_id();
+
+ Future<APIResult> result =
+ callUpdateFramework(&mesos, frameworkInfoWithNewRole, {}, *constraints);
+
+ AWAIT_READY(result);
+ EXPECT_EQ(result->status_code(), 400u);
+ EXPECT_TRUE(strings::contains(result->error(), "Offer constraints"));
+
+ Future<v1::master::Response::GetFrameworks> frameworks =
+ getFrameworks(master->get()->pid);
+
+ AWAIT_READY(frameworks);
+
+ ASSERT_EQ(frameworks->frameworks_size(), 1);
+
+ // The framework info should have remained the same, despite
+ // `updatedFrameworkInfo` on its own not containing any invalid updates.
+ FrameworkInfo expected = DEFAULT_FRAMEWORK_INFO;
+ *expected.mutable_id() = subscribed->framework_id();
+ EXPECT_NONE(::mesos::v1::typeutils::diff(
+ frameworks->frameworks(0).framework_info(), expected));
+}
+
} // namespace scheduler {
} // namespace v1 {