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:39 UTC
[2/4] mesos git commit: Added framework authorization for persistent
volumes.
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 {