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

[1/4] mesos git commit: Added DEFAULT_ALLOCATION_INTERVAL to master constants.

Repository: mesos
Updated Branches:
  refs/heads/master 35e03dd72 -> 08b5b10d8


Added DEFAULT_ALLOCATION_INTERVAL to master constants.

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


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

Branch: refs/heads/master
Commit: d8eb6186c0a3d75a80fde634c0639752fcac5549
Parents: 35e03dd
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Dec 18 16:54:40 2015 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Fri Dec 18 17:06:47 2015 -0800

----------------------------------------------------------------------
 src/master/constants.cpp | 1 +
 src/master/constants.hpp | 3 +++
 src/master/flags.cpp     | 2 +-
 3 files changed, 5 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/d8eb6186/src/master/constants.cpp
----------------------------------------------------------------------
diff --git a/src/master/constants.cpp b/src/master/constants.cpp
index 98ea7c8..589f2ee 100644
--- a/src/master/constants.cpp
+++ b/src/master/constants.cpp
@@ -45,6 +45,7 @@ const std::string MASTER_INFO_JSON_LABEL = "json.info";
 const Duration ZOOKEEPER_SESSION_TIMEOUT = Seconds(10);
 const std::string DEFAULT_AUTHENTICATOR = "crammd5";
 const std::string DEFAULT_ALLOCATOR = "HierarchicalDRF";
+const Duration DEFAULT_ALLOCATION_INTERVAL = Seconds(1);
 const std::string DEFAULT_AUTHORIZER = "local";
 
 } // namespace master {

http://git-wip-us.apache.org/repos/asf/mesos/blob/d8eb6186/src/master/constants.hpp
----------------------------------------------------------------------
diff --git a/src/master/constants.hpp b/src/master/constants.hpp
index cc38dfc..f1624e1 100644
--- a/src/master/constants.hpp
+++ b/src/master/constants.hpp
@@ -125,6 +125,9 @@ extern const std::string DEFAULT_AUTHENTICATOR;
 // Name of the default, HierarchicalDRF authenticator.
 extern const std::string DEFAULT_ALLOCATOR;
 
+// The default interval between allocations.
+extern const Duration DEFAULT_ALLOCATION_INTERVAL;
+
 // Name of the default, local authorizer.
 extern const std::string DEFAULT_AUTHORIZER;
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/d8eb6186/src/master/flags.cpp
----------------------------------------------------------------------
diff --git a/src/master/flags.cpp b/src/master/flags.cpp
index a8861f3..501ca8d 100644
--- a/src/master/flags.cpp
+++ b/src/master/flags.cpp
@@ -174,7 +174,7 @@ mesos::internal::master::Flags::Flags()
       "allocation_interval",
       "Amount of time to wait between performing\n"
       " (batch) allocations (e.g., 500ms, 1sec, etc).",
-      Seconds(1));
+      DEFAULT_ALLOCATION_INTERVAL);
 
   add(&Flags::cluster,
       "cluster",


[3/4] mesos git commit: Fixed handling of multiple offer operations in PersistentVolumeTest.SendingCheckpointResourcesMessage.

Posted by ji...@apache.org.
Fixed handling of multiple offer operations in
PersistentVolumeTest.SendingCheckpointResourcesMessage.

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


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

Branch: refs/heads/master
Commit: ac4a5689623dfc38f31b4cbe2be2e445cbd19698
Parents: 36ecc9e
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Dec 18 17:07:50 2015 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Fri Dec 18 17:07:50 2015 -0800

----------------------------------------------------------------------
 src/tests/persistent_volume_tests.cpp | 43 +++++++++++++++++++++++++++---
 1 file changed, 39 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/ac4a5689/src/tests/persistent_volume_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/persistent_volume_tests.cpp b/src/tests/persistent_volume_tests.cpp
index d8c60ff..2fb5781 100644
--- a/src/tests/persistent_volume_tests.cpp
+++ b/src/tests/persistent_volume_tests.cpp
@@ -97,7 +97,12 @@ TEST_F(PersistentVolumeTest, SendingCheckpointResourcesMessage)
   FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
   frameworkInfo.set_role("role1");
 
-  Try<PID<Master>> master = StartMaster(MasterFlags({frameworkInfo}));
+  // Create a master.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.allocation_interval = Milliseconds(50);
+  masterFlags.roles = frameworkInfo.role();
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
   ASSERT_SOME(master);
 
   slave::Flags slaveFlags = CreateSlaveFlags();
@@ -115,7 +120,7 @@ TEST_F(PersistentVolumeTest, SendingCheckpointResourcesMessage)
   Future<vector<Offer>> offers;
   EXPECT_CALL(sched, resourceOffers(&driver, _))
     .WillOnce(FutureArg<1>(&offers))
-    .WillRepeatedly(Return());        // Ignore subsequent offers.
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
 
   driver.start();
 
@@ -145,20 +150,50 @@ TEST_F(PersistentVolumeTest, SendingCheckpointResourcesMessage)
       "id2",
       "path2");
 
+  // We use the filter explicitly here so that the resources will not
+  // be filtered for 5 seconds (by default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  // Create the persistent volumes via `acceptOffers`.
   driver.acceptOffers(
       {offer.id()},
       {CREATE(volume1),
-       CREATE(volume2),
-       DESTROY(volume1)});
+       CREATE(volume2)},
+      filters);
+
+  // Expect an offer containing the persistent volumes.
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
 
   // NOTE: Currently, we send one message per operation. But this is
   // an implementation detail which is subject to change.
   AWAIT_READY(message1);
   EXPECT_EQ(Resources(message1.get().resources()), volume1);
 
+  // Await the `CheckpointResourcesMessage` and ensure that it contains
+  // both volume1 and volume2.
   AWAIT_READY(message2);
   EXPECT_EQ(Resources(message2.get().resources()), volume1 + volume2);
 
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Expect that the offer contains the persistent volumes we created.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume1));
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume2));
+
+  // Destroy `volume1`.
+  driver.acceptOffers(
+      {offer.id()},
+      {DESTROY(volume1)},
+      filters);
+
+  // Await the `CheckpointResourcesMessage` and ensure that it contains
+  // volume2 but not volume1.
   AWAIT_READY(message3);
   EXPECT_EQ(Resources(message3.get().resources()), volume2);
 


[4/4] mesos git commit: Added documentation for RESERVE, UNRESERVE, CREATE, and DESTROY authorization.

Posted by ji...@apache.org.
Added documentation for RESERVE, UNRESERVE, CREATE, and DESTROY
authorization.

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


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

Branch: refs/heads/master
Commit: 08b5b10d8f5dffe479331ae51b533e8924c4c090
Parents: ac4a568
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Dec 18 17:08:22 2015 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Fri Dec 18 17:08:22 2015 -0800

----------------------------------------------------------------------
 docs/authorization.md     | 14 ++++++++++++--
 docs/persistent-volume.md |  6 ++++--
 docs/reservation.md       | 10 ++++++----
 3 files changed, 22 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/08b5b10d/docs/authorization.md
----------------------------------------------------------------------
diff --git a/docs/authorization.md b/docs/authorization.md
index 0b108bf..9009228 100644
--- a/docs/authorization.md
+++ b/docs/authorization.md
@@ -10,6 +10,8 @@ Authorization currently allows
  2. Frameworks to launch tasks/executors as authorized _users_.
  3. Authorized _principals_ to shutdown frameworks through the "/teardown" HTTP endpoint.
  4. Authorized _principals_ to set quotas through the "/quota" HTTP endpoint.
+ 5. Authorized _principals_ to reserve and unreserve resources through the "/reserve" and "/unreserve" HTTP endpoints, as well as with the `RESERVE` and `UNRESERVE` offer operations.
+ 6. Authorized _principals_ to create and destroy persistent volumes through the `CREATE` and `DESTROY` offer operations.
 
 
 ## ACLs
@@ -24,18 +26,26 @@ The currently supported `Actions` are:
 2. "run_tasks": Run tasks/executors
 3. "shutdown_frameworks": Shutdown frameworks
 4. "set_quotas": Set quotas
+5. "reserve_resources": Reserve resources
+6. "unreserve_resources": Unreserve resources
+7. "create_volumes": Create persistent volumes
+8. "destroy_volumes": Destroy persistent volumes
 
 The currently supported `Subjects` are:
 
 1. "principals"
-	- Framework principals (used by "register_frameworks" and "run_tasks" actions)
-	- Usernames (used by "shutdown_frameworks" and "set_quotas" actions)
+	- Framework principals (used by "register_frameworks", "run_tasks", "reserve", "unreserve", "create_volumes", and "destroy_volumes" actions)
+	- Usernames (used by "shutdown_frameworks", "set_quotas", "reserve", "unreserve", "create_volumes", and "destroy_volumes" actions)
 
 The currently supported `Objects` are:
 
 1. "roles": Resource [roles](roles.md) that framework can register with (used by "register_frameworks" and "set_quotas" actions)
 2. "users": Unix user to launch the task/executor as (used by "run_tasks" actions)
 3. "framework_principals": Framework principals that can be shutdown by HTTP POST (used by "shutdown_frameworks" actions).
+4. "resources": Resources that can be reserved. Currently the only types considered by the default authorizer are `ANY` and `NONE` (used by "reserves" action).
+5. "reserver_principals": Framework principals whose reserved resources can be unreserved (used by "unreserves" action).
+6. "volume_types": Types of volumes that can be created by a given principal. Currently the only types considered by the default authorizer are `ANY` and `NONE` (used by "create_volumes" action).
+7. "creator_principals": Principals whose persistent volumes can be destroyed (used by "destroy_volumes" action).
 
 > NOTE: Both `Subjects` and `Objects` can be either an array of strings or one of the special values `ANY` or `NONE`.
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/08b5b10d/docs/persistent-volume.md
----------------------------------------------------------------------
diff --git a/docs/persistent-volume.md b/docs/persistent-volume.md
index cf7a6bb..766e62a 100644
--- a/docs/persistent-volume.md
+++ b/docs/persistent-volume.md
@@ -27,8 +27,10 @@ regarding reservation mechanisms available in Mesos.
 
 Persistent volumes can be created by __operators__ and authorized
 __frameworks__. We require a `principal` from the operator or framework in order
-to authenticate/authorize the operations. [Authorization](authorization.md) is
-specified via the existing ACL mechanism. (___Coming Soon___)
+to authenticate/authorize the operations. Permissions are specified via the
+existing ACL mechanism. To use authorization with reserve/unreserve operations,
+the Mesos master must be configured with the desired ACLs. For more information,
+see the [authorization documentation](authorization.md).
 
 * `Offer::Operation::Create` and `Offer::Operation::Destroy` messages are
   available for __frameworks__ to send back via the `acceptOffers` API as a

http://git-wip-us.apache.org/repos/asf/mesos/blob/08b5b10d/docs/reservation.md
----------------------------------------------------------------------
diff --git a/docs/reservation.md b/docs/reservation.md
index de44766..a5dbc0a 100644
--- a/docs/reservation.md
+++ b/docs/reservation.md
@@ -39,15 +39,17 @@ __NOTE:__ This feature is supported for backwards compatibility.
 
 ## Dynamic Reservation (since 0.23.0)
 
-As mentioned in [Static Reservation](#static-reservation-since-0140), specifying the
-reserved resources via the `--resources` flag makes the reservation static.
+As mentioned in [Static Reservation](#static-reservation-since-0140), specifying
+the reserved resources via the `--resources` flag makes the reservation static.
 This is, statically reserved resources cannot be reserved for another role nor
 be unreserved. Dynamic Reservation enables operators and authorized frameworks
 to reserve and unreserve resources post slave-startup.
 
 We require a `principal` from the operator or framework in order to
-authenticate/authorize the operations. [Authorization](authorization.md) is
-specified via the existing ACL mechanism. (_Coming Soon_)
+authenticate/authorize the operations. Permissions are specified via the
+existing ACL mechanism. To use authorization with reserve/unreserve operations,
+the Mesos master must be configured with the desired ACLs. For more information,
+see the [authorization documentation](authorization.md).
 
 * `Offer::Operation::Reserve` and `Offer::Operation::Unreserve` messages are
   available for __frameworks__ to send back via the `acceptOffers` API as a


[2/4] mesos git commit: Added framework authorization for persistent volumes.

Posted by ji...@apache.org.
Added framework authorization for persistent volumes.

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


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

Branch: refs/heads/master
Commit: 36ecc9ed3c6ec76cfd8c188938987981ca7c2a06
Parents: d8eb618
Author: Greg Mann <gr...@mesosphere.io>
Authored: Fri Dec 18 16:44:43 2015 -0800
Committer: Jie Yu <yu...@gmail.com>
Committed: Fri Dec 18 17:06:48 2015 -0800

----------------------------------------------------------------------
 src/master/master.cpp                 |  86 +++-
 src/tests/persistent_volume_tests.cpp | 693 +++++++++++++++++++++++++++++
 2 files changed, 771 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/36ecc9ed/src/master/master.cpp
----------------------------------------------------------------------
diff --git a/src/master/master.cpp b/src/master/master.cpp
index 3ceab51..8e29135 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -3108,10 +3108,32 @@ void Master::accept(
 
         break;
       }
-      case Offer::Operation::CREATE:
-      case Offer::Operation::DESTROY:
-        // TODO(mpark): Implement authorization for Create/Destroy.
+
+      // The CREATE operation allows the creation of a persistent volume.
+      case Offer::Operation::CREATE: {
+        Option<string> principal = framework->info.has_principal()
+          ? framework->info.principal()
+          : Option<string>::none();
+
+        futures.push_back(
+            authorizeCreateVolume(
+                operation.create(), principal));
+
+        break;
+      }
+
+      // The DESTROY operation allows the destruction of a persistent volume.
+      case Offer::Operation::DESTROY: {
+        Option<string> principal = framework->info.has_principal()
+          ? framework->info.principal()
+          : Option<string>::none();
+
+        futures.push_back(
+            authorizeDestroyVolume(
+                operation.destroy(), principal));
+
         break;
+      }
     }
   }
 
@@ -3295,9 +3317,33 @@ void Master::_accept(
       }
 
       case Offer::Operation::CREATE: {
+        Future<bool> authorization = authorizations.front();
+        authorizations.pop_front();
+
+        CHECK(!authorization.isDiscarded());
+
+        if (authorization.isFailed()) {
+          // TODO(greggomann): We may want to retry this failed authorization
+          // request rather than dropping it immediately.
+          drop(framework,
+               operation,
+               "Authorization of principal '" + framework->info.principal() +
+               "' to create persistent volumes failed: " +
+               authorization.failure());
+
+          continue;
+        } else if (!authorization.get()) {
+          drop(framework,
+               operation,
+               "Not authorized to create persistent volumes as '" +
+                 framework->info.principal() + "'");
+
+          continue;
+        }
+
+        // Make sure this create operation is valid.
         Option<Error> error = validation::operation::validate(
-            operation.create(),
-            slave->checkpointedResources);
+            operation.create(), slave->checkpointedResources);
 
         if (error.isSome()) {
           drop(framework, operation, error.get().message);
@@ -3321,9 +3367,33 @@ void Master::_accept(
       }
 
       case Offer::Operation::DESTROY: {
+        Future<bool> authorization = authorizations.front();
+        authorizations.pop_front();
+
+        CHECK(!authorization.isDiscarded());
+
+        if (authorization.isFailed()) {
+          // TODO(greggomann): We may want to retry this failed authorization
+          // request rather than dropping it immediately.
+          drop(framework,
+               operation,
+               "Authorization of principal '" + framework->info.principal() +
+               "' to destroy persistent volumes failed: " +
+               authorization.failure());
+
+          continue;
+        } else if (!authorization.get()) {
+          drop(framework,
+               operation,
+               "Not authorized to destroy persistent volumes as '" +
+                 framework->info.principal() + "'");
+
+          continue;
+        }
+
+        // Make sure this destroy operation is valid.
         Option<Error> error = validation::operation::validate(
-            operation.destroy(),
-            slave->checkpointedResources);
+            operation.destroy(), slave->checkpointedResources);
 
         if (error.isSome()) {
           drop(framework, operation, error.get().message);
@@ -3332,7 +3402,7 @@ void Master::_accept(
 
         Try<Resources> resources = _offeredResources.apply(operation);
         if (resources.isError()) {
-          drop(framework, operation, error.get().message);
+          drop(framework, operation, resources.error());
           continue;
         }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/36ecc9ed/src/tests/persistent_volume_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/persistent_volume_tests.cpp b/src/tests/persistent_volume_tests.cpp
index 01b3c13..d8c60ff 100644
--- a/src/tests/persistent_volume_tests.cpp
+++ b/src/tests/persistent_volume_tests.cpp
@@ -34,6 +34,7 @@
 
 #include <stout/os/exists.hpp>
 
+#include "master/constants.hpp"
 #include "master/flags.hpp"
 #include "master/master.hpp"
 
@@ -713,6 +714,698 @@ TEST_F(PersistentVolumeTest, SlaveRecovery)
   Shutdown();
 }
 
+
+// This test verifies that the `create` and `destroy` operations complete
+// successfully when authorization succeeds.
+TEST_F(PersistentVolumeTest, GoodACLCreateThenDestroy)
+{
+  // Manipulate the clock manually in order to
+  // control the timing of the offer cycle.
+  Clock::pause();
+
+  ACLs acls;
+
+  // This ACL declares that the principal of `DEFAULT_CREDENTIAL`
+  // can create any persistent volumes.
+  mesos::ACL::CreateVolume* create = acls.add_create_volumes();
+  create->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+  create->mutable_volume_types()->set_type(mesos::ACL::Entity::ANY);
+
+  // This ACL declares that the principal of `DEFAULT_CREDENTIAL`
+  // can destroy its own persistent volumes.
+  mesos::ACL::DestroyVolume* destroy = acls.add_destroy_volumes();
+  destroy->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+  destroy->mutable_creator_principals()->add_values(
+      DEFAULT_CREDENTIAL.principal());
+
+  // We use the filter explicitly here so that the resources will not
+  // be filtered for 5 seconds (by default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
+  frameworkInfo.set_role("role1");
+
+  // Create a master.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+  masterFlags.roles = frameworkInfo.role();
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // Create a slave. Resources are being statically reserved because persistent
+  // volume creation requires reserved resources.
+  slave::Flags slaveFlags = CreateSlaveFlags();
+  slaveFlags.resources = "cpus:2;mem:1024;disk(role1):2048";
+
+  Try<PID<Slave>> slave = StartSlave(slaveFlags);
+  ASSERT_SOME(slave);
+
+  // Create a scheduler/framework.
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(&driver, _, _));
+
+  // Expect an offer from the slave.
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  Offer offer = offers.get()[0];
+
+  Resources volume = createPersistentVolume(
+      Megabytes(128),
+      "role1",
+      "id1",
+      "path1");
+
+  Future<CheckpointResourcesMessage> checkpointResources1 =
+    FUTURE_PROTOBUF(CheckpointResourcesMessage(), _, slave.get());
+
+  // Create the persistent volume using `acceptOffers`.
+  driver.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Await the CheckpointResourceMessage response after the volume is created
+  // and check that it contains the volume.
+  AWAIT_READY(checkpointResources1);
+  EXPECT_EQ(Resources(checkpointResources1.get().resources()), volume);
+
+  // Expect an offer containing the persistent volume.
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  // Await the offer containing the persistent volume.
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume was created successfully.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+  EXPECT_TRUE(
+      os::exists(slave::paths::getPersistentVolumePath(
+          slaveFlags.work_dir,
+          "role1",
+          "id1")));
+
+  Future<CheckpointResourcesMessage> checkpointResources2 =
+    FUTURE_PROTOBUF(CheckpointResourcesMessage(), _, slave.get());
+
+  // Destroy the persistent volume using `acceptOffers`.
+  driver.acceptOffers(
+      {offer.id()},
+      {DESTROY(volume)},
+      filters);
+
+  AWAIT_READY(checkpointResources2);
+  EXPECT_FALSE(
+      Resources(checkpointResources2.get().resources()).contains(volume));
+
+  // Expect an offer that does not contain the persistent volume.
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume is not in the offer.
+  EXPECT_FALSE(Resources(offer.resources()).contains(volume));
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}
+
+
+// This test verifies that the `create` and `destroy` operations complete
+// successfully when authorization succeeds and no principal is provided.
+TEST_F(PersistentVolumeTest, GoodACLNoPrincipal)
+{
+  // Manipulate the clock manually in order to
+  // control the timing of the offer cycle.
+  Clock::pause();
+
+  ACLs acls;
+
+  // This ACL declares that any principal (and also frameworks without a
+  // principal) can create persistent volumes.
+  mesos::ACL::CreateVolume* create = acls.add_create_volumes();
+  create->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  create->mutable_volume_types()->set_type(mesos::ACL::Entity::ANY);
+
+  // This ACL declares that any principal (and also frameworks without a
+  // principal) can destroy persistent volumes.
+  mesos::ACL::DestroyVolume* destroy = acls.add_destroy_volumes();
+  destroy->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  destroy->mutable_creator_principals()->set_type(mesos::ACL::Entity::ANY);
+
+  // We use the filter explicitly here so that the resources will not be
+  // filtered for 5 seconds (by default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  // Create a `FrameworkInfo` with no principal.
+  FrameworkInfo frameworkInfo;
+  frameworkInfo.set_name("no-principal");
+  frameworkInfo.set_user(os::user().get());
+  frameworkInfo.set_role("role1");
+
+  // Create a master. Since the framework has no
+  // principal, we don't authenticate frameworks.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+  masterFlags.roles = frameworkInfo.role();
+  masterFlags.authenticate_frameworks = false;
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // Create a slave. Resources are being statically reserved because persistent
+  // volume creation requires reserved resources.
+  slave::Flags slaveFlags = CreateSlaveFlags();
+  slaveFlags.resources = "cpus:2;mem:1024;disk(role1):2048";
+
+  Try<PID<Slave>> slave = StartSlave(slaveFlags);
+  ASSERT_SOME(slave);
+
+  // Create a scheduler/framework.
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(&driver, _, _));
+
+  // Expect an offer from the slave.
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  Offer offer = offers.get()[0];
+
+  Resources volume = createPersistentVolume(
+      Megabytes(128),
+      "role1",
+      "id1",
+      "path1");
+
+  Future<CheckpointResourcesMessage> checkpointResources1 =
+    FUTURE_PROTOBUF(CheckpointResourcesMessage(), _, slave.get());
+
+  // Create the persistent volume using `acceptOffers`.
+  driver.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Await the CheckpointResourceMessage response after the volume is created
+  // and check that it contains the volume.
+  AWAIT_READY(checkpointResources1);
+  EXPECT_EQ(Resources(checkpointResources1.get().resources()), volume);
+
+  // Expect an offer containing the persistent volume.
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  // Await the offer containing the persistent volume.
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume was successfully created.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+  EXPECT_TRUE(
+      os::exists(slave::paths::getPersistentVolumePath(
+          slaveFlags.work_dir,
+          "role1",
+          "id1")));
+
+  Future<CheckpointResourcesMessage> checkpointResources2 =
+    FUTURE_PROTOBUF(CheckpointResourcesMessage(), _, slave.get());
+
+  // Destroy the persistent volume using `acceptOffers`.
+  driver.acceptOffers(
+      {offer.id()},
+      {DESTROY(volume)},
+      filters);
+
+  AWAIT_READY(checkpointResources2);
+
+  // Expect an offer that does not contain the persistent volume.
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume was not created
+  EXPECT_FALSE(Resources(offer.resources()).contains(volume));
+  EXPECT_FALSE(
+      Resources(checkpointResources2.get().resources()).contains(volume));
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}
+
+
+// This test verifies that `create` and `destroy` operations fail as expected
+// when authorization fails and no principal is supplied.
+TEST_F(PersistentVolumeTest, BadACLNoPrincipal)
+{
+  // Manipulate the clock manually in order to
+  // control the timing of the offer cycle.
+  Clock::pause();
+
+  ACLs acls;
+
+  // This ACL declares that the principal of `DEFAULT_FRAMEWORK_INFO`
+  // can create persistent volumes.
+  mesos::ACL::CreateVolume* create1 = acls.add_create_volumes();
+  create1->mutable_principals()->add_values(DEFAULT_FRAMEWORK_INFO.principal());
+  create1->mutable_volume_types()->set_type(mesos::ACL::Entity::ANY);
+
+  // This ACL declares that any other principals
+  // cannot create persistent volumes.
+  mesos::ACL::CreateVolume* create2 = acls.add_create_volumes();
+  create2->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  create2->mutable_volume_types()->set_type(mesos::ACL::Entity::NONE);
+
+  // We use this filter so that resources will not
+  // be filtered for 5 seconds (by default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  // Create a `FrameworkInfo` with no principal.
+  FrameworkInfo frameworkInfo1;
+  frameworkInfo1.set_name("no-principal");
+  frameworkInfo1.set_user(os::user().get());
+  frameworkInfo1.set_role("role1");
+
+  // Create a `FrameworkInfo` with a principal.
+  FrameworkInfo frameworkInfo2 = DEFAULT_FRAMEWORK_INFO;
+  frameworkInfo2.set_role("role1");
+
+  // Create a master. Since one framework has no
+  // principal, we don't authenticate frameworks.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+  masterFlags.roles = frameworkInfo1.role();
+  masterFlags.authenticate_frameworks = false;
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // Create a slave.
+  slave::Flags slaveFlags = CreateSlaveFlags();
+  slaveFlags.resources = "cpus:2;mem:1024;disk(role1):2048";
+
+  Try<PID<Slave>> slave = StartSlave(slaveFlags);
+  ASSERT_SOME(slave);
+
+  // Create a scheduler/framework.
+  MockScheduler sched1;
+  MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get());
+
+  EXPECT_CALL(sched1, registered(&driver1, _, _));
+
+  // Expect an offer from the slave.
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver1.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  Offer offer = offers.get()[0];
+
+  Resources volume = createPersistentVolume(
+      Megabytes(128),
+      "role1",
+      "id1",
+      "path1");
+
+  // Attempt to create the persistent volume using `acceptOffers`.
+  driver1.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume is not contained in this offer.
+  EXPECT_FALSE(Resources(offer.resources()).contains(volume));
+
+  // Decline the offer and suppress so the second
+  // framework will receive the offer instead.
+  driver1.declineOffer(offer.id(), filters);
+  driver1.suppressOffers();
+
+  // Create a second framework which can create volumes.
+  MockScheduler sched2;
+  MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get());
+
+  EXPECT_CALL(sched2, registered(&driver2, _, _));
+
+  // Expect an offer to the second framework.
+  EXPECT_CALL(sched2, resourceOffers(&driver2, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver2.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Create the persistent volume using `acceptOffers`.
+  driver2.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched2, resourceOffers(&driver2, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume is contained in this offer.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+
+  // Decline, suppress, and revive offers appropriately so that `driver1` can
+  // receive an offer.
+  driver2.declineOffer(offer.id(), filters);
+  driver2.suppressOffers();
+  driver1.reviveOffers();
+
+  // Expect an offer to the first framework.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Attempt to destroy the persistent volume using `acceptOffers`.
+  driver1.acceptOffers(
+      {offer.id()},
+      {DESTROY(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  // Check that the persistent volume is still contained in this offer.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+
+  driver1.stop();
+  driver1.join();
+
+  driver2.stop();
+  driver2.join();
+
+  Shutdown();
+}
+
+
+// This test verifies that `create` and `destroy` operations
+// get dropped if authorization fails.
+TEST_F(PersistentVolumeTest, BadACLDropCreateAndDestroy)
+{
+  // Manipulate the clock manually in order to
+  // control the timing of the offer cycle.
+  Clock::pause();
+
+  ACLs acls;
+
+  // This ACL declares that the principal 'creator-principal'
+  // can create persistent volumes.
+  mesos::ACL::CreateVolume* create1 = acls.add_create_volumes();
+  create1->mutable_principals()->add_values("creator-principal");
+  create1->mutable_volume_types()->set_type(mesos::ACL::Entity::ANY);
+
+  // This ACL declares that all other principals
+  // cannot create any persistent volumes.
+  mesos::ACL::CreateVolume* create = acls.add_create_volumes();
+  create->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+  create->mutable_volume_types()->set_type(mesos::ACL::Entity::NONE);
+
+  // We use the filter explicitly here so that the resources will not
+  // be filtered for 5 seconds (by default).
+  Filters filters;
+  filters.set_refuse_seconds(0);
+
+  // Create a `FrameworkInfo` that cannot create or destroy volumes.
+  FrameworkInfo frameworkInfo1 = DEFAULT_FRAMEWORK_INFO;
+  frameworkInfo1.set_role("role1");
+
+  // Create a `FrameworkInfo` that can create volumes.
+  FrameworkInfo frameworkInfo2;
+  frameworkInfo2.set_name("creator-framework");
+  frameworkInfo2.set_user(os::user().get());
+  frameworkInfo2.set_role("role1");
+  frameworkInfo2.set_principal("creator-principal");
+
+  // Create a master.
+  master::Flags masterFlags = CreateMasterFlags();
+  masterFlags.acls = acls;
+  masterFlags.roles = frameworkInfo1.role();
+  masterFlags.authenticate_frameworks = false;
+
+  Try<PID<Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // Create a slave.
+  slave::Flags slaveFlags = CreateSlaveFlags();
+  slaveFlags.resources = "cpus:2;mem:1024;disk(role1):2048";
+
+  Try<PID<Slave>> slave = StartSlave(slaveFlags);
+  ASSERT_SOME(slave);
+
+  // Create a scheduler/framework.
+  MockScheduler sched1;
+  MesosSchedulerDriver driver1(&sched1, frameworkInfo1, master.get());
+
+  EXPECT_CALL(sched1, registered(&driver1, _, _));
+
+  // Expect an offer from the slave.
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver1.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  Offer offer = offers.get()[0];
+
+  Resources volume = createPersistentVolume(
+      Megabytes(128),
+      "role1",
+      "id1",
+      "path1");
+
+  // Attempt to create a persistent volume using `acceptOffers`.
+  driver1.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume is not contained in this offer.
+  EXPECT_FALSE(Resources(offer.resources()).contains(volume));
+
+  // Decline the offer and suppress so the second
+  // framework will receive the offer instead.
+  driver1.declineOffer(offer.id(), filters);
+  driver1.suppressOffers();
+
+  // Create a second framework which can create volumes.
+  MockScheduler sched2;
+  MesosSchedulerDriver driver2(&sched2, frameworkInfo2, master.get());
+
+  EXPECT_CALL(sched2, registered(&driver2, _, _));
+
+  // Expect an offer to the second framework.
+  EXPECT_CALL(sched2, resourceOffers(&driver2, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  driver2.start();
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Create a persistent volume using `acceptOffers`.
+  driver2.acceptOffers(
+      {offer.id()},
+      {CREATE(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched2, resourceOffers(&driver2, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Check that the persistent volume is contained in this offer.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+
+  // Decline, suppress, and revive offers appropriately so that `driver1` can
+  // receive an offer.
+  driver2.declineOffer(offer.id(), filters);
+  driver2.suppressOffers();
+  driver1.reviveOffers();
+
+  // Expect an offer to the first framework.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  // Advance the clock to generate an offer.
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  offer = offers.get()[0];
+
+  // Attempt to destroy the persistent volume using `acceptOffers`.
+  driver1.acceptOffers(
+      {offer.id()},
+      {DESTROY(volume)},
+      filters);
+
+  // Expect another offer.
+  EXPECT_CALL(sched1, resourceOffers(&driver1, _))
+    .WillOnce(FutureArg<1>(&offers));
+
+  Clock::settle();
+  Clock::advance(masterFlags.allocation_interval);
+
+  AWAIT_READY(offers);
+  EXPECT_FALSE(offers.get().empty());
+
+  // Check that the persistent volume is still contained in this offer.
+  EXPECT_TRUE(Resources(offer.resources()).contains(volume));
+
+  driver1.stop();
+  driver1.join();
+
+  driver2.stop();
+  driver2.join();
+
+  Shutdown();
+}
+
 } // namespace tests {
 } // namespace internal {
 } // namespace mesos {