You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bm...@apache.org on 2018/10/15 22:31:55 UTC

[mesos] 03/04: Added validation of QuotaRequest.

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

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

commit 4189143ab0ea778bf59b85fc883ec1cda7482e95
Author: Benjamin Mahler <bm...@apache.org>
AuthorDate: Mon Feb 5 18:56:43 2018 -0800

    Added validation of QuotaRequest.
    
    This obviates the need for the old-style validation of quotaInfo,
    but leaves the latter in order to keep the v0 code path untouched.
    A TODO has been added to remove the old validation and replace it
    with the one in this patch.
    
    Review: https://reviews.apache.org/r/65784
---
 src/master/quota.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/master/quota.hpp |  26 +++++++++++++
 2 files changed, 130 insertions(+)

diff --git a/src/master/quota.cpp b/src/master/quota.cpp
index bef18c0..671626c 100644
--- a/src/master/quota.cpp
+++ b/src/master/quota.cpp
@@ -21,9 +21,11 @@
 #include <mesos/mesos.hpp>
 #include <mesos/resources.hpp>
 #include <mesos/roles.hpp>
+#include <mesos/values.hpp>
 
 #include <stout/error.hpp>
 #include <stout/option.hpp>
+#include <stout/set.hpp>
 
 #include "common/resources_utils.hpp"
 
@@ -188,6 +190,108 @@ Option<Error> quotaInfo(const QuotaInfo& quotaInfo)
 
 } // namespace validation {
 
+Option<Error> validate(const QuotaRequest& request)
+{
+  if (!request.has_role()) {
+    return Error("'QuotaRequest.role' must be set");
+  }
+
+  // Check the provided role is valid.
+  Option<Error> error = roles::validate(request.role());
+  if (error.isSome()) {
+    return Error("Invalid 'QuotaRequest.role': " + error->message);
+  }
+
+  // Disallow quota for '*' role.
+  //
+  // TODO(alexr): Consider allowing setting quota for '*' role,
+  // see MESOS-3938.
+  if (request.role() == "*") {
+    return Error("Invalid 'QuotaRequest.role': setting quota for the"
+                 " default '*' role is not supported");
+  }
+
+  // Define a helper for use against guarantee and limit.
+  auto validateQuotaResources = [](
+      const RepeatedPtrField<Resource>& resources) -> Option<Error> {
+    hashset<string> names;
+
+    // Check that each resource does not contain fields
+    // that are disallowed for quota resources.
+    foreach (const Resource& resource, resources) {
+      if (resource.has_reservation()) {
+        return Error("'Resource.reservation' must not be set");
+      }
+
+      if (resource.reservations_size() > 0) {
+        return Error("'Resource.reservations' must not be set");
+      }
+
+      if (resource.has_disk()) {
+        return Error("'Resource.disk' must not be set");
+      }
+
+      if (resource.has_revocable()) {
+        return Error("'Resource.revocable' must not be set");
+      }
+
+      if (resource.type() != Value::SCALAR) {
+        return Error("'Resource.type' must be 'SCALAR'");
+      }
+
+      if (resource.has_shared()) {
+        return Error("'Resource.shared' must not be set");
+      }
+
+      if (resource.has_provider_id()) {
+        return Error("'Resource.provider_id' must not be set");
+      }
+
+      // Check that resource names do not repeat.
+      if (names.contains(resource.name())) {
+        return Error("Duplicate '" + resource.name() + "'; only a single"
+                     " entry for each resource is supported");
+      }
+
+      names.insert(resource.name());
+    }
+
+    // Finally, ensure the resources are valid.
+    return Resources::validate(resources);
+  };
+
+  error = validateQuotaResources(request.guarantee());
+  if (error.isSome()) {
+    return Error("Invalid 'QuotaRequest.guarantee': " + error->message);
+  }
+
+  error = validateQuotaResources(request.limit());
+  if (error.isSome()) {
+    return Error("Invalid 'QuotaRequest.limit': " + error->message);
+  }
+
+  // Validate that guarantee <= limit.
+  Resources guarantee = request.guarantee();
+  Resources limit = request.limit();
+
+  // This needs to be checked on a per-resource basis for those
+  // resources that are specified in both the guarantee and limit.
+  foreach (const string& name, guarantee.names() & limit.names()) {
+    Value::Scalar guaranteeScalar =
+      CHECK_NOTNONE(guarantee.get<Value::Scalar>(name));
+    Value::Scalar limitScalar =
+      CHECK_NOTNONE(limit.get<Value::Scalar>(name));
+
+    if (!(guaranteeScalar <= limitScalar)) {
+      return Error("'QuotaRequest.guarantee' (" + stringify(guarantee) + ")"
+                     " is not contained within the 'QuotaRequest.limit'"
+                     " (" + stringify(limit) + ")");
+    }
+  }
+
+  return None();
+}
+
 } // namespace quota {
 } // namespace master {
 } // namespace internal {
diff --git a/src/master/quota.hpp b/src/master/quota.hpp
index 40447bc..5cd2bb0 100644
--- a/src/master/quota.hpp
+++ b/src/master/quota.hpp
@@ -103,10 +103,36 @@ namespace validation {
 //   - Irrelevant fields in `Resources` are not set
 //     (e.g. `ReservationInfo`).
 //   - Request only contains scalar `Resources`.
+//
+// TODO(bmahler): Remove this in favor of `validate` below. This
+// requires some new logic outside of this function to prevent
+// users from setting `limit` explicitly in the old API and
+// setting the `limit` implicitly for users of the old API before
+// calling into this.
 Option<Error> quotaInfo(const mesos::quota::QuotaInfo& quotaInfo);
 
 } // namespace validation {
 
+/**
+ * A `QuotaRequest` is valid if the following conditions are met:
+ *
+ *   (1) The request has a valid non-"*" role.
+ *
+ *   (2) The guarantee and limit contain only valid non-revocable
+ *       scalar resources without reservations, disk info, and
+ *       only 1 entry for each resource name.
+ *
+ *   (3) If both guarantee and limit are set for a particular
+ *       resource, then guarantee <= limit for that resource.
+ *
+ * TODO(bmahler): Remove the old validation function in favor of
+ * this one. This requires some new logic outside of this function
+ * to prevent users from setting `limit` explicitly in the old API
+ * and setting the `limit` implicitly for users of the old API before
+ * calling into this.
+ */
+Option<Error> validate(const mesos::quota::QuotaRequest& request);
+
 } // namespace quota {
 } // namespace master {
 } // namespace internal {