You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bb...@apache.org on 2019/02/15 14:16:12 UTC

[mesos] 02/05: Validated static framework offer filters.

This is an automated email from the ASF dual-hosted git repository.

bbannier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git

commit a74cf0765c30659a71e0e7a431ddc0d1ecedb8d0
Author: Benjamin Bannier <be...@mesosphere.io>
AuthorDate: Wed Feb 6 15:05:02 2019 +0100

    Validated static framework offer filters.
    
    This patch adds validation to framework-specified static offer filters.
    In particular we enforce that minimal resource quantities cannot be
    negative.
    
    Review: https://reviews.apache.org/r/69862/
---
 src/common/validation.cpp             |  38 ++++++++++++
 src/common/validation.hpp             |   2 +
 src/master/validation.cpp             |  28 +++++++++
 src/master/validation.hpp             |   5 +-
 src/tests/common_validation_tests.cpp | 105 ++++++++++++++++++++++++++++++++++
 src/tests/master_validation_tests.cpp |  26 +++++++++
 6 files changed, 201 insertions(+), 3 deletions(-)

diff --git a/src/common/validation.cpp b/src/common/validation.cpp
index 4a64fc8..b622a32 100644
--- a/src/common/validation.cpp
+++ b/src/common/validation.cpp
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <cctype>
+#include <cmath>
 
 #include <mesos/executor/executor.hpp>
 
@@ -614,6 +615,43 @@ Option<Error> validateExecutorCall(const mesos::executor::Call& call)
   UNREACHABLE();
 }
 
+
+Option<Error> validateOfferFilters(const OfferFilters& offerFilters)
+{
+  if (offerFilters.has_min_allocatable_resources()) {
+    foreach (
+        const OfferFilters::ResourceQuantities& quantities,
+        offerFilters.min_allocatable_resources().quantities()) {
+      if (quantities.quantities().empty()) {
+        return Error("Resource quantities must contain at least one quantity");
+      }
+
+      // Use `auto` instead of `protobuf::MapPair<strinf, Value::>` since
+      // `foreach` is a macro and does not allow angle brackets.
+      foreach (auto&& quantity, quantities.quantities()) {
+        const Value::Scalar& scalar = quantity.second;
+
+        // We do not allow negative quantities in offer filters.
+        if (scalar.value() < 0) {
+          return Error("Negative resource quantities are not allowed");
+        }
+
+        // Resource quantities cannot have NaN values.
+        if (std::isnan(scalar.value())) {
+          return Error("Resource quantities cannot be NaN");
+        }
+
+        // We do not allow infinite quantities in offer filters.
+        if (std::isinf(scalar.value())) {
+          return Error("Infinite resource quantities are not allowed");
+        }
+      }
+    }
+  }
+
+  return None();
+}
+
 } // namespace validation {
 } // namespace common {
 } // namespace internal {
diff --git a/src/common/validation.hpp b/src/common/validation.hpp
index 5929ac2..d09528f 100644
--- a/src/common/validation.hpp
+++ b/src/common/validation.hpp
@@ -69,6 +69,8 @@ Option<Error> validateCheckStatusInfo(const CheckStatusInfo& checkStatusInfo);
 
 Option<Error> validateExecutorCall(const mesos::executor::Call& call);
 
+Option<Error> validateOfferFilters(const OfferFilters& offerFilters);
+
 } // namespace validation {
 } // namespace common {
 } // namespace internal {
diff --git a/src/master/validation.cpp b/src/master/validation.cpp
index 4a699f0..565d6d4 100644
--- a/src/master/validation.cpp
+++ b/src/master/validation.cpp
@@ -518,10 +518,32 @@ Option<Error> validateFrameworkId(const FrameworkInfo& frameworkInfo)
   return common::validation::validateID(frameworkInfo.id().value());
 }
 
+
+Option<Error> validateOfferFilters(const FrameworkInfo& frameworkInfo)
+{
+  // Use `auto` in place of `protobuf::MapPair<string, Value::Scalar>`
+  // below since `foreach` is a macro and cannot contain angle brackets.
+  foreach (auto&& filter, frameworkInfo.offer_filters()) {
+    const OfferFilters& offerFilters = filter.second;
+
+    Option<Error> error =
+      common::validation::validateOfferFilters(offerFilters);
+
+    if (error.isSome()) {
+      return error;
+    }
+  }
+
+  return None();
+}
+
 } // namespace internal {
 
+
 Option<Error> validate(const mesos::FrameworkInfo& frameworkInfo)
 {
+  // TODO(jay_guo): This currently only validates the role(s),
+  // framework ID and offer filters, validate more fields!
   Option<Error> error = internal::validateRoles(frameworkInfo);
 
   if (error.isSome()) {
@@ -534,6 +556,12 @@ Option<Error> validate(const mesos::FrameworkInfo& frameworkInfo)
     return error;
   }
 
+  error = internal::validateOfferFilters(frameworkInfo);
+
+  if (error.isSome()) {
+    return error;
+  }
+
   return None();
 }
 
diff --git a/src/master/validation.hpp b/src/master/validation.hpp
index a5bdff6..71748c1 100644
--- a/src/master/validation.hpp
+++ b/src/master/validation.hpp
@@ -105,12 +105,11 @@ Option<Error> validateRoles(const mesos::FrameworkInfo& frameworkInfo);
 
 Option<Error> validateFrameworkId(const mesos::FrameworkInfo& frameworkInfo);
 
+Option<Error> validateOfferFilters(const FrameworkInfo& frameworkInfo);
+
 } // namespace internal {
 
 // Validate a FrameworkInfo.
-//
-// TODO(jay_guo): This currently only validates
-// the role(s), validate more fields!
 Option<Error> validate(const mesos::FrameworkInfo& frameworkInfo);
 
 } // namespace framework {
diff --git a/src/tests/common_validation_tests.cpp b/src/tests/common_validation_tests.cpp
index 97466ab..214b993 100644
--- a/src/tests/common_validation_tests.cpp
+++ b/src/tests/common_validation_tests.cpp
@@ -14,6 +14,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <limits>
+
 #include <stout/gtest.hpp>
 
 #include "common/validation.hpp"
@@ -81,6 +83,109 @@ TEST(CommonValidationTest, Volume)
   }
 }
 
+
+TEST(CommonValidationTest, OfferFilters)
+{
+  // Offer filters can be empty.
+  EXPECT_NONE(common::validation::validateOfferFilters(OfferFilters()));
+
+  {
+    // Offer filters can have empty minimal allocatable resources to
+    // communicate that any resource should be considered allocatable.
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources();
+    EXPECT_NONE(common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // Minimal allocatable resources cannot have empty quantities.
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()->add_quantities();
+    EXPECT_SOME_EQ(
+        Error("Resource quantities must contain at least one quantity"),
+        common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // A resource quantity cannot be negative.
+    Value::Scalar scalar;
+    scalar.set_value(-2);
+
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()
+      ->add_quantities()
+      ->mutable_quantities()
+      ->insert({"cpus", scalar});
+    EXPECT_SOME_EQ(
+        Error("Negative resource quantities are not allowed"),
+        common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // A resource quantity must be a number.
+    static_assert(
+        std::numeric_limits<double>::has_quiet_NaN,
+        "Expected double to have a quiet NaN representation");
+
+    Value::Scalar scalar;
+    scalar.set_value(std::numeric_limits<double>::quiet_NaN());
+
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()
+      ->add_quantities()
+      ->mutable_quantities()
+      ->insert({"cpus", scalar});
+    EXPECT_SOME_EQ(
+        Error("Resource quantities cannot be NaN"),
+        common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // A resource quantity cannot be (positive) infinite.
+    static_assert(
+        std::numeric_limits<double>::has_infinity,
+        "Expected double to have a representation of infinity");
+
+    Value::Scalar scalar;
+    scalar.set_value(std::numeric_limits<double>::infinity());
+
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()
+      ->add_quantities()
+      ->mutable_quantities()
+      ->insert({"cpus", scalar});
+    EXPECT_SOME_EQ(
+        Error("Infinite resource quantities are not allowed"),
+        common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // A resource quantity can be zero.
+    Value::Scalar scalar;
+    scalar.set_value(0);
+
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()
+      ->add_quantities()
+      ->mutable_quantities()
+      ->insert({"cpus", scalar});
+    EXPECT_NONE(common::validation::validateOfferFilters(offerFilters));
+  }
+
+  {
+    // A resource quantity can be positive.
+    Value::Scalar scalar;
+    scalar.set_value(2);
+
+    OfferFilters offerFilters;
+    offerFilters.mutable_min_allocatable_resources()
+      ->add_quantities()
+      ->mutable_quantities()
+      ->insert({"cpus", scalar});
+    EXPECT_NONE(common::validation::validateOfferFilters(offerFilters));
+  }
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {
diff --git a/src/tests/master_validation_tests.cpp b/src/tests/master_validation_tests.cpp
index b34e88f..9568d39 100644
--- a/src/tests/master_validation_tests.cpp
+++ b/src/tests/master_validation_tests.cpp
@@ -4718,6 +4718,32 @@ TEST_F(FrameworkInfoValidationTest, ValidateFrameworkID)
 }
 
 
+// This test validates that framework cannot configure negative
+// resources in their minimal allocatable resources offer filters.
+TEST_F(FrameworkInfoValidationTest, ValidateOfferFilters)
+{
+  Value::Scalar scalar;
+  scalar.set_value(-2);
+
+  OfferFilters offerFilters;
+  offerFilters.mutable_min_allocatable_resources()
+    ->add_quantities()
+    ->mutable_quantities()
+    ->insert({"cpus", scalar});
+
+  FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+
+  ASSERT_FALSE(frameworkInfo.roles().empty());
+
+  frameworkInfo.mutable_offer_filters()->insert(
+      {frameworkInfo.roles(0), offerFilters});
+
+  EXPECT_SOME_EQ(
+      Error("Negative resource quantities are not allowed"),
+      framework::validate(frameworkInfo));
+}
+
+
 // This test ensures that ia framework cannot use the
 // `FrameworkInfo.roles` field without providing the
 // MULTI_ROLE capability.