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/04/11 00:30:19 UTC

mesos git commit: Refactored resources chopping logic in allocator.

Repository: mesos
Updated Branches:
  refs/heads/master df89829de -> 21166da14


Refactored resources chopping logic in allocator.

Introduced a `shrinkResources()` lambda in allocator
so that the same resource chopping logic can be re-used
in the future, in particular, when introducing the quota
limit.

Review: https://reviews.apache.org/r/66044/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/21166da1
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/21166da1
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/21166da1

Branch: refs/heads/master
Commit: 21166da14fc8bbe501de0dd36deacaf2b0f0e3eb
Parents: df89829
Author: Meng Zhu <mz...@mesosphere.io>
Authored: Tue Apr 10 17:30:00 2018 -0700
Committer: Benjamin Mahler <bm...@apache.org>
Committed: Tue Apr 10 17:30:00 2018 -0700

----------------------------------------------------------------------
 src/master/allocator/mesos/hierarchical.cpp | 179 +++++++++++++----------
 1 file changed, 102 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/21166da1/src/master/allocator/mesos/hierarchical.cpp
----------------------------------------------------------------------
diff --git a/src/master/allocator/mesos/hierarchical.cpp b/src/master/allocator/mesos/hierarchical.cpp
index 32e8895..6ec3d30 100644
--- a/src/master/allocator/mesos/hierarchical.cpp
+++ b/src/master/allocator/mesos/hierarchical.cpp
@@ -1574,6 +1574,45 @@ void HierarchicalAllocatorProcess::__allocate()
     return quotaRoleSorter->allocationScalarQuantities(role).toUnreserved();
   };
 
+  // Returns the result of shrinking the provided resources down to the
+  // target scalar quantities. If a resource does not have a target
+  // quantity provided, it will not be shrunk.
+  //
+  // Note that some resources are indivisible (e.g. MOUNT volume) and
+  // may be excluded in entirety in order to achieve the target size
+  // (this may lead to the result size being smaller than the target size).
+  //
+  // Note also that there may be more than one result that satisfies
+  // the target sizes (e.g. need to exclude 1 of 2 disks); this function
+  // will make a random choice in these cases.
+  auto shrinkResources =
+    [](const Resources& resources,
+       hashmap<string, Value::Scalar> targetScalarQuantites) {
+    google::protobuf::RepeatedPtrField<Resource>
+      resourceVector = resources;
+
+    random_shuffle(resourceVector.begin(), resourceVector.end());
+
+    Resources result;
+    foreach (Resource& resource, resourceVector) {
+      if (!targetScalarQuantites.contains(resource.name())) {
+        // Resource that has no target quantity is left as is.
+        result += std::move(resource);
+        continue;
+      }
+
+      Option<Value::Scalar> limitScalar =
+        targetScalarQuantites.get(resource.name());
+
+      if (Resources::shrink(&resource, limitScalar.get())) {
+        targetScalarQuantites[resource.name()] -= limitScalar.get();
+        result += std::move(resource);
+      }
+    }
+
+    return result;
+  };
+
   // To enforce quota, we keep track of consumed quota for roles with a
   // non-default quota.
   //
@@ -1836,93 +1875,79 @@ void HierarchicalAllocatorProcess::__allocate()
           Resources(quota.info.guarantee()) -
             rolesConsumedQuotaScalarQuantites.get(role).getOrElse(Resources());
 
-        // Unreserved resources that are tentatively going to be
-        // allocated towards this role's quota. These resources may
-        // not get allocated due to framework filters.
-        // These are __quantities__ with no meta-data.
-        Resources newQuotaAllocationScalarQuantities;
+        Resources unreserved = available.nonRevocable().unreserved();
 
-        // We put resource that this role has no quota guarantee for in
-        // `nonQuotaGuaranteeResources` tentatively.
-        Resources nonQuotaGuaranteeResources;
+        // First, allocate resources up to a role's quota guarantee.
 
-        Resources unreserved = available.nonRevocable().unreserved();
+        hashmap<string, Value::Scalar> unsatisfiedQuotaGuaranteeScalarLimit;
+        foreach (const string& name, unsatisfiedQuotaGuarantee.names()) {
+          unsatisfiedQuotaGuaranteeScalarLimit[name] +=
+            CHECK_NOTNONE(unsatisfiedQuotaGuarantee.get<Value::Scalar>(name));
+        }
+
+        Resources newQuotaAllocation =
+          unreserved.filter([&](const Resource& resource) {
+            return
+              unsatisfiedQuotaGuaranteeScalarLimit.contains(resource.name());
+          });
+
+        newQuotaAllocation = shrinkResources(newQuotaAllocation,
+            unsatisfiedQuotaGuaranteeScalarLimit);
+
+        resources += newQuotaAllocation;
+
+        // We only include the non-quota guarantee resources (with headroom
+        // taken into account) if this role is getting any other resources
+        // as well i.e. it is getting either some quota guarantee resources or
+        // a reservation. Otherwise, this role is not going to get any
+        // allocation. We can safely `continue` here.
+        if (resources.empty()) {
+          continue;
+        }
+
+        // Second, allocate scalar resources with unset quota while maintaining
+        // the quota headroom.
 
         set<string> quotaGuaranteeResourceNames =
           Resources(quota.info.guarantee()).names();
 
-        // When "chopping" resources, there is more than 1 "chop" that
-        // can be done to satisfy the guarantee. Consider the case with
-        // two disks of 1GB, one is PATH and another is MOUNT. And a
-        // role has a "disk" quota guarantee of 1GB. We could pick either of
-        // the disks here, but not both.
-        //
-        // In order to avoid repeatedly choosing the same "chop" of
-        // the resources each time we allocate, we introduce some
-        // randomness by shuffling the resources.
-        google::protobuf::RepeatedPtrField<Resource>
-          resourceVector = unreserved;
-        random_shuffle(resourceVector.begin(), resourceVector.end());
-
-        foreach (Resource& resource, resourceVector) {
-          if (resource.type() != Value::SCALAR) {
-            // We currently do not support quota for non-scalar resources,
-            // add it to `nonQuotaGuaranteeResources`.
-            nonQuotaGuaranteeResources += resource;
-            continue;
-          }
+        Resources nonQuotaGuaranteeResources =
+          unreserved.filter(
+              [&quotaGuaranteeResourceNames] (const Resource& resource) {
+                return quotaGuaranteeResourceNames.count(resource.name()) == 0;
+              }
+          );
 
-          if (quotaGuaranteeResourceNames.count(resource.name()) == 0) {
-            // Allocating resource that this role has NO quota guarantee for,
-            // the limit concern here is that it should not break the
-            // quota headroom.
-            //
-            // Allocation Limit = Available Headroom - Required Headroom -
-            //                    Tentative Allocation to Role
-            Resources upperLimitScalarQuantities =
-              availableHeadroom - requiredHeadroom -
-              (newQuotaAllocationScalarQuantities +
-                nonQuotaGuaranteeResources.createStrippedScalarQuantity());
-
-            Option<Value::Scalar> limitScalar =
-              upperLimitScalarQuantities.get<Value::Scalar>(resource.name());
-
-            if (limitScalar.isNone()) {
-              continue; // Already have a headroom deficit.
-            }
+        // Allocation Limit = Available Headroom - Required Headroom
+        Resources headroomResourcesLimit = availableHeadroom - requiredHeadroom;
 
-            if (Resources::shrink(&resource, limitScalar.get())) {
-              nonQuotaGuaranteeResources += resource;
-            }
-          } else {
-            // Allocating resource that this role has quota guarantee for,
-            // the limit concern is that it should not exceed this
-            // role's unsatisfied quota guarantee.
-            Resources upperLimitScalarQuantities =
-              unsatisfiedQuotaGuarantee -
-                newQuotaAllocationScalarQuantities;
-
-            Option<Value::Scalar> limitScalar =
-              upperLimitScalarQuantities.get<Value::Scalar>(resource.name());
-
-            if (limitScalar.isNone()) {
-              continue; // Quota guarantee already met.
-            }
+        hashmap<string, Value::Scalar> headroomScalarLimit;
+        foreach (const string& name, headroomResourcesLimit.names()) {
+          headroomScalarLimit[name] =
+            CHECK_NOTNONE(headroomResourcesLimit.get<Value::Scalar>(name));
+        }
 
-            if (Resources::shrink(&resource, limitScalar.get())) {
-              resources += resource;
-              newQuotaAllocationScalarQuantities +=
-                Resources(resource).createStrippedScalarQuantity();
+        // If a resource type is absent in `headroomScalarLimit`, it means this
+        // type of resource is already in quota headroom deficit and we make
+        // no more allocations. They are filtered out.
+        nonQuotaGuaranteeResources = nonQuotaGuaranteeResources.filter(
+            [&] (const Resource& resource) {
+              return headroomScalarLimit.contains(resource.name());
             }
-          }
-        }
+          );
 
-        // We include the non-quota guarantee resources (with headroom taken
-        // into account) if this role is being allocated some resources
-        // already: either some quota guarantee resources or a reservation.
-        if (!resources.empty()) {
-          resources += nonQuotaGuaranteeResources;
-        }
+        nonQuotaGuaranteeResources =
+          shrinkResources(nonQuotaGuaranteeResources, headroomScalarLimit);
+
+        resources += nonQuotaGuaranteeResources;
+
+        // Lastly, allocate non-scalar resources--we currently do not support
+        // setting quota for non-scalar resources. They are always allocated
+        // in full.
+        resources +=
+          unreserved.filter([] (const Resource& resource) {
+            return resource.type() != Value::SCALAR;
+          });
 
         // It is safe to break here, because all frameworks under a role would
         // consider the same resources, so in case we don't have allocatable
@@ -1979,7 +2004,7 @@ void HierarchicalAllocatorProcess::__allocate()
         // role's guarantee should be subtracted. Allocation of reserved
         // resources or resources that this role has unset guarantee do not
         // affect `requiredHeadroom`.
-        requiredHeadroom -= newQuotaAllocationScalarQuantities;
+        requiredHeadroom -= newQuotaAllocation.createStrippedScalarQuantity();
 
         // `availableHeadroom` counts total unreserved non-revocable resources
         // in the cluster.