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


The following commit(s) were added to refs/heads/master by this push:
     new ec2d1cb  Moved creating authorization Object out of 
Master::authorize*reserve.
ec2d1cb is described below

commit ec2d1cb1f291b6ffa4e814151ad4a82a0daa950a
Author: Andrei Sekretenko <[email protected]>
AuthorDate: Tue Jan 7 13:42:17 2020 -0500

    Moved creating authorization Object out of Master::authorize*reserve.
    
    NOTE: This patch also partially addresses MESOS-9562 issue
    by modifying the logic of authorizing UNRESERVE operations.
    Now, if some reservations have no principal, both authorization of
    UNRESERVE_RESOURCES for ANY object and authorizations for unreserving
    all reservations that have principals will be requested.
    
    Review: https://reviews.apache.org/r/71864/
---
 src/master/authorization.cpp | 122 ++++++++++++++++++++++++++
 src/master/authorization.hpp |  24 ++++++
 src/master/http.cpp          |   6 +-
 src/master/master.cpp        | 199 ++-----------------------------------------
 src/master/master.hpp        |  43 ----------
 5 files changed, 155 insertions(+), 239 deletions(-)

diff --git a/src/master/authorization.cpp b/src/master/authorization.cpp
index 96f6a1c..d97288a 100644
--- a/src/master/authorization.cpp
+++ b/src/master/authorization.cpp
@@ -23,6 +23,8 @@ using std::ostream;
 using std::string;
 using std::vector;
 
+using google::protobuf::RepeatedPtrField;
+
 namespace mesos {
 namespace authorization {
 
@@ -97,6 +99,126 @@ string getReservationRole(const Resource& resource)
 }
 
 
+void ActionObject::pushUnreserveActionObjects(
+    const Resources& resources,
+    vector<ActionObject>* result)
+{
+  bool hasReservationsWithoutPrincipal = false;
+
+  for (const Resource& resource : resources) {
+    // NOTE: We rely on the master to ensure that the resource is in the
+    // post-reservation-refinement format.
+    CHECK(!resource.has_role()) << resource;
+    CHECK(!resource.has_reservation()) << resource;
+
+    if (!resource.reservations().empty() &&
+        resource.reservations().rbegin()->has_principal()) {
+      result->push_back(fromResourceWithLegacyValue(
+          authorization::UNRESERVE_RESOURCES,
+          resource,
+          resource.reservations().rbegin()->principal()));
+    } else {
+      hasReservationsWithoutPrincipal = true;
+    }
+  }
+
+  if (hasReservationsWithoutPrincipal) {
+    result->push_back(ActionObject(authorization::UNRESERVE_RESOURCES, 
None()));
+  }
+}
+
+
+vector<ActionObject>
+ActionObject::unreserve(const Offer::Operation::Unreserve& unreserve)
+{
+  vector<ActionObject> result;
+  pushUnreserveActionObjects(unreserve.resources(), &result);
+  // NOTE: In some cases, such as UNRESERVE with no resources
+  // (which is invalid) or resources reserved without a principal
+  // by an old version of Mesos, an empty vector will be returned.
+  return result;
+}
+
+
+vector<ActionObject> ActionObject::reserve(
+    const Offer::Operation::Reserve& reserve)
+{
+  std::vector<ActionObject> result;
+  auto pushReserveActionObjects =
+    [&result](const Resources& resources) mutable {
+      // The operation will be authorized if the entity is allowed to make
+      // reservations for all unique roles included in `resources`.
+      //
+      // TODO(asekretenko): This approach assumes that resources with identical
+      // reservation roles are equivalent from the authorizers' point of view;
+      // in future this assumption might become incorrect. As there is no 
reason
+      // other than performance to leave only resources with unique roles, this
+      // filtration can probably be avoided after implementing synchronous
+      // authorization (see MESOS-10056).
+      hashset<string> roles;
+
+      foreach (const Resource& resource, resources) {
+        const string role = getReservationRole(resource);
+
+        if (!roles.contains(role)) {
+          roles.insert(role);
+          result.push_back(fromResourceWithLegacyValue(
+            authorization::RESERVE_RESOURCES, resource, role));
+        }
+      }
+    };
+
+  // We support `Reserve` operations with either `source` set or unset. If
+  // `source` is unset (an older API), it is possible to send calls which
+  // contain also resources whose reservations are unmodified; in that case
+  // all `Reserve` `resources` will be authorized. In the case where `source`
+  // is set we follow a narrower contract and e.g., only accept resources
+  // whose reservations are all modified, and require identical modifications
+  // for all passed `Resource`s.
+  //
+  // This e.g., means that we cannot upgrade calls without `source` to
+  // calls with `source` and use uniform handling. Instead we branch
+  // on whether `source` was set to select the authorization handling.
+  // Each fundamental reservation added in with with-`source` case can
+  // then be authorized using the historic, wider contract.
+  if (reserve.source().empty())
+  {
+    pushReserveActionObjects(reserve.resources());
+    return result;
+  }
+
+  Resources source = reserve.source();
+  const Resources target = reserve.resources();
+  const Resources ancestor =
+    Resources::getReservationAncestor(source, target);
+
+  // We request UNRESERVE_RESOURCES to bring `source` to `ancestor`.
+  while (source != ancestor) {
+    pushUnreserveActionObjects(source, &result);
+    source = source.popReservation();
+  }
+
+  // We request RESERVE_RESOURCES to bring `ancestor` to `target`.
+  const RepeatedPtrField<Resource::ReservationInfo>& targetReservations =
+    reserve.resources(0).reservations();
+  const RepeatedPtrField<Resource::ReservationInfo>& ancestorReservations =
+    RepeatedPtrField<Resource>(ancestor).begin()->reservations();
+
+  // Skip reservations common among `source` and `resources`.
+  auto it = targetReservations.begin();
+  std::advance(it, ancestorReservations.size());
+
+  for (; it != targetReservations.end(); ++it) {
+    source = source.pushReservation(*it);
+    pushReserveActionObjects(source);
+  }
+
+  // NOTE: In some cases, such as RESERVE with source identical to target,
+  // an empty vector will be returned.
+  return result;
+}
+
+
 vector<ActionObject> ActionObject::createVolume(
     const Offer::Operation::Create& create)
 {
diff --git a/src/master/authorization.hpp b/src/master/authorization.hpp
index d2b96ef..58bf214 100644
--- a/src/master/authorization.hpp
+++ b/src/master/authorization.hpp
@@ -55,6 +55,12 @@ public:
   // resources are in post-reservation-refinement format.
   // Otherwise, they  may crash the program.
 
+  static std::vector<ActionObject> unreserve(
+      const Offer::Operation::Unreserve& unreserve);
+
+  static std::vector<ActionObject> reserve(
+      const Offer::Operation::Reserve& reserve);
+
   static std::vector<ActionObject> createVolume(
       const Offer::Operation::Create& create);
 
@@ -98,6 +104,24 @@ private:
       Action action,
       const Resource& resource,
       std::string value_);
+
+  // Helper method for `reserve()`/`unreserve()`.
+  //
+  // For each resource reserved with a principal, pushes into `result` an
+  // ActionObject needed for unreserving it. If there are resources reserved
+  // without a principal, an UNRESERVE_RESOURCE for ANY object is also added
+  // (see MESOS-9562).
+  //
+  // NOTE: Since the UNRESERVE operation only "pops" one reservation off the
+  // stack of reservations, only principals of the most refined reservations
+  // (i.e. ones that will be unreserved) are used.
+  //
+  // NOTE: Currently, validation of RESERVE operations prevents creating
+  // reservation without a principal, so they should not exist in new
+  // clusters.
+  static void pushUnreserveActionObjects(
+      const Resources& resources,
+      std::vector<ActionObject>* result);
 };
 
 // Outputs human-readable description of an ActionObject.
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 93ec096..16dbed3 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -2827,7 +2827,8 @@ Future<Response> Master::Http::_reserve(
         error->message);
   }
 
-  return master->authorizeReserveResources(operation.reserve(), principal)
+  return master->authorize(
+      principal, ActionObject::reserve(operation.reserve()))
     .then(defer(master->self(), [=](bool authorized) -> Future<Response> {
       if (!authorized) {
         return Forbidden();
@@ -5578,7 +5579,8 @@ Future<Response> Master::Http::_unreserve(
     return BadRequest("Invalid UNRESERVE operation: " + error->message);
   }
 
-  return master->authorizeUnreserveResources(operation.unreserve(), principal)
+  return master->authorize(
+      principal, ActionObject::unreserve(operation.unreserve()))
     .then(defer(master->self(), [=](bool authorized) -> Future<Response> {
       if (!authorized) {
         return Forbidden();
diff --git a/src/master/master.cpp b/src/master/master.cpp
index d3c2889..c9ae0cd 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -3754,185 +3754,6 @@ Future<bool> Master::authorize(
 }
 
 
-Future<bool> Master::authorizeReserveResources(
-    const Offer::Operation::Reserve& reserve,
-    const Option<Principal>& principal)
-{
-  if (authorizer.isNone()) {
-    return true; // Authorization is disabled.
-  }
-
-  authorization::Request request;
-  request.set_action(authorization::RESERVE_RESOURCES);
-
-  Option<authorization::Subject> subject = createSubject(principal);
-  if (subject.isSome()) {
-    request.mutable_subject()->CopyFrom(subject.get());
-  }
-
-  vector<Future<bool>> authorizations;
-
-  // We support `Reserve` operations with either `source` set or unset. If
-  // `source` is unset (an older API), it is possible to send calls which
-  // contain also resources whose reservations are unmodified; in that case all
-  // `Reserve` `resources` will be authorized. In the case where `source` is 
set
-  // we follow a narrower contract and e.g., only accept resources whose
-  // reservations are all modified, and require identical modifications for all
-  // passed `Resource`s.
-  //
-  // This e.g., means that we cannot upgrade calls without `source` to
-  // calls with `source` and use uniform handling. Instead we branch
-  // on whether `source` was set to select the authorization handling.
-  // Each fundamental reservation added in with with-`source` case can
-  // then be authorized using the historic, wider contract.
-  if (reserve.source().empty()) {
-    // The operation will be authorized if the entity is allowed to make
-    // reservations for all roles included in `reserve.resources`.
-    // Add an element to `request.roles` for each unique role in the resources.
-    hashset<string> roles;
-
-    foreach (const Resource& resource, reserve.resources()) {
-      // NOTE: We rely on the master to ensure that the resource is in the
-      // post-reservation-refinement format. If there is a stack of
-      // reservations, we perform authorization for the role of the most 
refined
-      // reservation, since we only support "pushing" one reservation at a 
time.
-      // That is, all of the previous reservations must have already been
-      // authorized.
-      //
-      // NOTE: If there is no reservation, we authorize the resource with the
-      // default role '*' for backward compatibility.
-      CHECK(!resource.has_role()) << resource;
-      CHECK(!resource.has_reservation()) << resource;
-
-      const string role = Resources::isReserved(resource)
-        ? Resources::reservationRole(resource) : "*";
-
-      if (!roles.contains(role)) {
-        roles.insert(role);
-
-        request.mutable_object()->mutable_resource()->CopyFrom(resource);
-
-        // We also set the deprecated `object.value` field to support legacy
-        // authorizers that have not been upgraded to look at 
`object.resource`.
-        request.mutable_object()->set_value(role);
-
-        authorizations.push_back(authorizer.get()->authorized(request));
-      }
-    }
-
-    LOG(INFO) << "Authorizing principal '"
-              << (principal.isSome() ? stringify(principal.get()) : "ANY")
-              << "' to reserve resources '" << reserve.resources() << "'";
-  } else {
-    Resources source = reserve.source();
-    const Resources target = reserve.resources();
-    const Resources ancestor =
-      Resources::getReservationAncestor(source, target);
-
-    // Authorize `UNRESERVE` operations bringing `source` to `ancestor`.
-    while (source != ancestor) {
-      Offer::Operation::Unreserve unreserve;
-      unreserve.mutable_resources()->CopyFrom(source);
-
-      authorizations.push_back(
-          authorizeUnreserveResources(unreserve, principal));
-
-      source = source.popReservation();
-    }
-
-    // Authorize `RESERVE` operations bringing `ancestor` to `target`.
-    const RepeatedPtrField<Resource::ReservationInfo>& targetReservations =
-      reserve.resources(0).reservations();
-    const RepeatedPtrField<Resource::ReservationInfo>& ancestorReservations =
-      RepeatedPtrField<Resource>(ancestor).begin()->reservations();
-
-    // Skip reservations common among `source` and `resources`.
-    auto it = targetReservations.begin();
-    std::advance(it, ancestorReservations.size());
-
-    for (; it != targetReservations.end(); ++it) {
-      source = source.pushReservation(*it);
-
-      // We do not set `source` here to trigger the previous branch.
-      Offer::Operation::Reserve reserve;
-      reserve.mutable_resources()->CopyFrom(source);
-
-      authorizations.push_back(authorizeReserveResources(reserve, principal));
-    }
-  }
-
-  // NOTE: Empty authorizations are not valid and are checked by a validator.
-  // However under certain circumstances, this method can be called before
-  // the validation occur and the case must be considered non erroneous.
-  // TODO(arojas): Consider ensuring that `validate()` is called before
-  // `authorizeReserveResources` so a `CHECK(!roles.empty())` can be added.
-  if (authorizations.empty()) {
-    return authorizer.get()->authorized(request);
-  }
-
-  return authorization::collectAuthorizations(authorizations);
-}
-
-
-Future<bool> Master::authorizeUnreserveResources(
-    const Offer::Operation::Unreserve& unreserve,
-    const Option<Principal>& principal)
-{
-  if (authorizer.isNone()) {
-    return true; // Authorization is disabled.
-  }
-
-  authorization::Request request;
-  request.set_action(authorization::UNRESERVE_RESOURCES);
-
-  Option<authorization::Subject> subject = createSubject(principal);
-  if (subject.isSome()) {
-    request.mutable_subject()->CopyFrom(subject.get());
-  }
-
-  vector<Future<bool>> authorizations;
-  foreach (const Resource& resource, unreserve.resources()) {
-    // NOTE: We rely on the master to ensure that the resource is in the
-    // post-reservation-refinement format. Since the UNRESERVE operation only
-    // "pops" one reservation off the stack of reservations, we perform
-    // authorization for the principal of the most refined reservation, which
-    // will be unreserved (i.e., popped off the stack).
-    //
-    // NOTE: Since authorization happens __before__ validation, we must check
-    // here that this resource has a reservation. If not, the error will be
-    // caught during validation.
-    CHECK(!resource.has_role()) << resource;
-    CHECK(!resource.has_reservation()) << resource;
-
-    Option<string> principal;
-    if (!resource.reservations().empty() &&
-        resource.reservations().rbegin()->has_principal()) {
-      principal = resource.reservations().rbegin()->principal();
-    }
-
-    if (principal.isSome()) {
-      request.mutable_object()->mutable_resource()->CopyFrom(resource);
-
-      // We also set the deprecated `object.value` field to support legacy
-      // authorizers that have not been upgraded to look at `object.resource`.
-      request.mutable_object()->set_value(principal.get());
-
-      authorizations.push_back(authorizer.get()->authorized(request));
-    }
-  }
-
-  LOG(INFO) << "Authorizing principal '"
-            << (principal.isSome() ? stringify(principal.get()) : "ANY")
-            << "' to unreserve resources '" << unreserve.resources() << "'";
-
-  if (authorizations.empty()) {
-    return authorizer.get()->authorized(request);
-  }
-
-  return authorization::collectAuthorizations(authorizations);
-}
-
-
 Future<bool> Master::authorizeSlave(
     const SlaveInfo& slaveInfo,
     const Option<Principal>& principal)
@@ -3974,7 +3795,7 @@ Future<bool> Master::authorizeSlave(
     Offer::Operation::Reserve reserve;
     reserve.mutable_resources()->CopyFrom(slaveInfo.resources());
     authorizations.push_back(
-        authorizeReserveResources(reserve, principal));
+        authorize(principal, ActionObject::reserve(reserve)));
   }
 
   return authorization::collectAuthorizations(authorizations);
@@ -4570,26 +4391,16 @@ void Master::accept(
 
       // The RESERVE operation allows a principal to reserve resources.
       case Offer::Operation::RESERVE: {
-        Option<Principal> principal = framework->info.has_principal()
-          ? Principal(framework->info.principal())
-          : Option<Principal>::none();
-
-        futures.push_back(
-            authorizeReserveResources(
-                operation.reserve(), principal));
+        futures.push_back(authorize(
+            principal, ActionObject::reserve(operation.reserve())));
 
         break;
       }
 
       // The UNRESERVE operation allows a principal to unreserve resources.
       case Offer::Operation::UNRESERVE: {
-        Option<Principal> principal = framework->info.has_principal()
-          ? Principal(framework->info.principal())
-          : Option<Principal>::none();
-
-        futures.push_back(
-            authorizeUnreserveResources(
-                operation.unreserve(), principal));
+        futures.push_back(authorize(
+            principal, ActionObject::unreserve(operation.unreserve())));
 
         break;
       }
diff --git a/src/master/master.hpp b/src/master/master.hpp
index be1a0dd..0ac3332 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -769,49 +769,6 @@ protected:
       const SlaveInfo& slaveInfo,
       const Option<process::http::authentication::Principal>& principal);
 
-
-  /**
-   * Authorizes a `RESERVE` operation.
-   *
-   * Returns whether the Reserve operation is authorized with the
-   * provided principal. This function is used for authorization of
-   * operations originating from both frameworks and operators. Note
-   * that operations may be validated AFTER authorization, so it's
-   * possible that `reserve` could be malformed.
-   *
-   * @param reserve The `RESERVE` operation to be performed.
-   * @param principal An `Option` containing the principal attempting
-   *     this operation.
-   *
-   * @return A `Future` containing a boolean value representing the
-   *     success or failure of this authorization. A failed `Future`
-   *     implies that validation of the operation did not succeed.
-   */
-  process::Future<bool> authorizeReserveResources(
-      const Offer::Operation::Reserve& reserve,
-      const Option<process::http::authentication::Principal>& principal);
-
-  /**
-   * Authorizes an `UNRESERVE` operation.
-   *
-   * Returns whether the Unreserve operation is authorized with the
-   * provided principal. This function is used for authorization of
-   * operations originating both from frameworks and operators. Note
-   * that operations may be validated AFTER authorization, so it's
-   * possible that `unreserve` could be malformed.
-   *
-   * @param unreserve The `UNRESERVE` operation to be performed.
-   * @param principal An `Option` containing the principal attempting
-   *     this operation.
-   *
-   * @return A `Future` containing a boolean value representing the
-   *     success or failure of this authorization. A failed `Future`
-   *     implies that validation of the operation did not succeed.
-   */
-  process::Future<bool> authorizeUnreserveResources(
-      const Offer::Operation::Unreserve& unreserve,
-      const Option<process::http::authentication::Principal>& principal);
-
   // Determine if a new executor needs to be launched.
   bool isLaunchExecutor (
       const ExecutorID& executorId,

Reply via email to