Added test for authorization actions for `RESIZE_VOLUME`. Review: https://reviews.apache.org/r/66532/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/360ae2f9 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/360ae2f9 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/360ae2f9 Branch: refs/heads/master Commit: 360ae2f95ef4850018843e88fd8adc1e847f6ce0 Parents: 9656329 Author: Zhitao Li <zhitaoli...@gmail.com> Authored: Thu May 3 17:04:57 2018 -0700 Committer: Chun-Hung Hsiao <chhs...@mesosphere.io> Committed: Thu May 3 17:04:57 2018 -0700 ---------------------------------------------------------------------- src/tests/authorization_tests.cpp | 294 +++++++++++++++++++++++ src/tests/persistent_volume_tests.cpp | 366 +++++++++++++++++++++++++++++ 2 files changed, 660 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/360ae2f9/src/tests/authorization_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/authorization_tests.cpp b/src/tests/authorization_tests.cpp index a76ad18..f6f7769 100644 --- a/src/tests/authorization_tests.cpp +++ b/src/tests/authorization_tests.cpp @@ -1977,6 +1977,300 @@ TYPED_TEST(AuthorizationTest, DestroyVolume) } +// Tests the authorization of ACLs used for resizing volumes. +TYPED_TEST(AuthorizationTest, ResizeVolume) +{ + ACLs acls; + + { + // Principal "foo" can resize volumes for any role. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("foo"); + acl->mutable_roles()->set_type(mesos::ACL::Entity::ANY); + } + + { + // Principal "bar" can only resize volumes for the "panda" role. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("bar"); + acl->mutable_roles()->add_values("panda"); + } + + { + // Principal "baz" cannot resize volumes. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("baz"); + acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE); + } + + { + // Principal "elizabeth-ii" can resize volumes for the "king" role and its + // nested ones. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("elizabeth-ii"); + acl->mutable_roles()->add_values("king/%"); + acl->mutable_roles()->add_values("king"); + } + + { + // Principal "charles" can resize volumes for any role below the "king/" + // role. Not in "king" itself. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("charles"); + acl->mutable_roles()->add_values("king/%"); + } + + { + // Principal "j-welby" can resize volumes only for the "king" role but + // not in any nested one. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->add_values("j-welby"); + acl->mutable_roles()->add_values("king"); + } + + { + // No other principals can resize volumes. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); + acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE); + } + + { + // No other principals can resize volumes. + mesos::ACL::ResizeVolume* acl = acls.add_resize_volumes(); + acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY); + acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE); + } + + // Create an `Authorizer` with the ACLs. + Try<Authorizer*> create = TypeParam::create(parameterize(acls)); + ASSERT_SOME(create); + Owned<Authorizer> authorizer(create.get()); + + // Principal "foo" can create volumes for any role, so this request will pass. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("foo"); + request.mutable_object()->set_value("awesome_role"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("foo"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("awesome_role"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + // Principal "bar" can create volumes for the "panda" role, + // so this request will pass. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("bar"); + request.mutable_object()->set_value("panda"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("bar"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("panda"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + // Principal "bar" cannot resize volumes for the "giraffe" role, + // so this request will fail. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("bar"); + request.mutable_object()->set_value("giraffe"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("bar"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("giraffe"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + + // Principal "baz" cannot resize volumes for any role, + // so this request will fail. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("baz"); + request.mutable_object()->set_value("panda"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("baz"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("panda"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + + // Principal "zelda" is not mentioned in the ACLs of the Authorizer, so it + // will be caught by the final ACL, which provides a default case that denies + // access for all other principals. This request will fail. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("zelda"); + request.mutable_object()->set_value("panda"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("zelda"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("panda"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + + // "elizabeth-ii" has full permissions for the "king" role as well as all + // its nested roles. She should be able to resize volumes in the next + // three blocks. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("elizabeth-ii"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("elizabeth-ii"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("elizabeth-ii"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince/duke"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + // "charles" doesn't have permissions for the "king" role, so the first + // test should fail. However he has permissions for "king"'s nested roles + // so the next two tests should succeed. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("charles"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("charles"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("charles"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince/duke"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + // "j-welby" only has permissions for the role "king" itself, but not + // for its nested roles, therefore only the first of the following three + // tests should succeed. + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("j-welby"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king"); + AWAIT_EXPECT_TRUE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("j-welby"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } + + { + authorization::Request request; + request.set_action(authorization::RESIZE_VOLUME); + request.mutable_subject()->set_value("j-welby"); + request.mutable_object() + ->mutable_resource() + ->mutable_reservations() + ->Add() + ->set_role("king/prince/duke"); + AWAIT_EXPECT_FALSE(authorizer->authorized(request)); + } +} + + // This tests the authorization of requests to update quotas. TYPED_TEST(AuthorizationTest, UpdateQuota) { http://git-wip-us.apache.org/repos/asf/mesos/blob/360ae2f9/src/tests/persistent_volume_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/persistent_volume_tests.cpp b/src/tests/persistent_volume_tests.cpp index 5b2a23c..477e6e2 100644 --- a/src/tests/persistent_volume_tests.cpp +++ b/src/tests/persistent_volume_tests.cpp @@ -985,6 +985,372 @@ TEST_P(PersistentVolumeTest, NonSpeculativeShrinkAndLaunch) } +// This test verifies that grow and shrink operations can complete +// successfully when authorization succeeds. +TEST_P(PersistentVolumeTest, GoodACLGrowThenShrink) +{ + if (GetParam() == MOUNT) { + // It is not possible to have a valid `GrowVolume` on a MOUNT disk because + // the volume must use up all disk space at `Create` and no space will be + // left for `addition`. Therefore we skip this test. + // TODO(zhitao): Make MOUNT a meaningful parameter value for this test, or + // create a new fixture to avoid testing against it. + return; + } + + Clock::pause(); + + ACLs acls; + + // This ACL declares that the principal of `DEFAULT_CREDENTIAL` + // can resize persistent volumes for DEFAULT_TEST_ROLE. + mesos::ACL::ResizeVolume* resize = acls.add_resize_volumes(); + resize->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); + resize->mutable_roles()->add_values(DEFAULT_TEST_ROLE); + + // Create a master. + master::Flags masterFlags = CreateMasterFlags(); + masterFlags.acls = acls; + + Try<Owned<cluster::Master>> master = StartMaster(masterFlags); + ASSERT_SOME(master); + + slave::Flags slaveFlags = CreateSlaveFlags(); + slaveFlags.resources = getSlaveResources(); + + Owned<MasterDetector> detector = master.get()->createDetector(); + Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO; + frameworkInfo.set_roles(0, DEFAULT_TEST_ROLE); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + Future<vector<Offer>> offersBeforeCreate; + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offersBeforeCreate)); + + driver.start(); + + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersBeforeCreate); + ASSERT_FALSE(offersBeforeCreate->empty()); + + Offer offer = offersBeforeCreate->at(0); + + // The disk spaces will be merged if the fixture parameter is `NONE`. + Bytes totalBytes = GetParam() == NONE ? Megabytes(4096) : Megabytes(2048); + + Bytes bytesDifference = Megabytes(512); + + // Construct a persistent volume which do not use up all disk resources. + Resource volume = createPersistentVolume( + getDiskResource(totalBytes - bytesDifference, 1), + "id1", + "path1", + None(), + frameworkInfo.principal()); + + Resource difference = getDiskResource(bytesDifference, 1); + + Resource grownVolume = createPersistentVolume( + getDiskResource(totalBytes, 1), + "id1", + "path1", + None(), + frameworkInfo.principal()); + + Future<vector<Offer>> offersAfterGrow; + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offersAfterGrow)); + + // We use the filter explicitly here so that the resources will not + // be filtered for 5 seconds (the default). + Filters filters; + filters.set_refuse_seconds(0); + + // Create a persistent volume then grow it. + driver.acceptOffers( + {offer.id()}, + {CREATE(volume), GROW_VOLUME(volume, difference)}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterGrow); + ASSERT_FALSE(offersAfterGrow->empty()); + + offer = offersAfterGrow->at(0); + + EXPECT_EQ( + allocatedResources(Resources(grownVolume), frameworkInfo.roles(0)), + Resources(offer.resources()).persistentVolumes()); + + EXPECT_FALSE( + Resources(offer.resources()).contains( + allocatedResources(difference, frameworkInfo.roles(0)))); + + Future<vector<Offer>> offersAfterShrink; + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offersAfterShrink)); + + // Shrink the volume back to original size. + driver.acceptOffers( + {offer.id()}, + {SHRINK_VOLUME(grownVolume, difference.scalar())}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterShrink); + ASSERT_FALSE(offersAfterShrink->empty()); + offer = offersAfterShrink->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), frameworkInfo.roles(0)), + Resources(offer.resources()).persistentVolumes()); + + EXPECT_TRUE( + Resources(offer.resources()).contains( + allocatedResources(difference, frameworkInfo.roles(0)))); + + Clock::resume(); + + driver.stop(); + driver.join(); +} + +// This test verifies that grow and shrink operations get dropped if +// authorization fails and no principal is supplied. +TEST_P(PersistentVolumeTest, BadACLDropGrowAndShrink) +{ + if (GetParam() == MOUNT) { + // It is not possible to have a valid `GrowVolume` on a MOUNT disk because + // the volume must use up all disk space at `Create` and no space will be + // left for `addition`. Therefore we skip this test. + // TODO(zhitao): Make MOUNT a meaningful parameter value for this test, or + // create a new fixture to avoid testing against it. + return; + } + + Clock::pause(); + + ACLs acls; + + // This ACL declares that no principal can resize any volume. + mesos::ACL::ResizeVolume* resize = acls.add_resize_volumes(); + resize->mutable_principals()->set_type(mesos::ACL::Entity::ANY); + resize->mutable_roles()->set_type(mesos::ACL::Entity::NONE); + + // Create a master. + master::Flags masterFlags = CreateMasterFlags(); + masterFlags.acls = acls; + + Try<Owned<cluster::Master>> master = StartMaster(masterFlags); + ASSERT_SOME(master); + + slave::Flags slaveFlags = CreateSlaveFlags(); + slaveFlags.resources = getSlaveResources(); + + Owned<MasterDetector> detector = master.get()->createDetector(); + Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), slaveFlags); + ASSERT_SOME(slave); + + // DEFAULT_FRAMEWORK_INFO uses DEFAULT_CREDENTIAL. + FrameworkInfo frameworkInfo1 = DEFAULT_FRAMEWORK_INFO; + frameworkInfo1.set_roles(0, DEFAULT_TEST_ROLE); + + MockScheduler sched1; + MesosSchedulerDriver driver1( + &sched1, frameworkInfo1, master.get()->pid, DEFAULT_CREDENTIAL); + + EXPECT_CALL(sched1, registered(&driver1, _, _)); + + Future<vector<Offer>> offersBeforeCreate; + EXPECT_CALL(sched1, resourceOffers(&driver1, _)) + .WillOnce(FutureArg<1>(&offersBeforeCreate)); + + driver1.start(); + + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersBeforeCreate); + ASSERT_FALSE(offersBeforeCreate->empty()); + + Offer offer = offersBeforeCreate->at(0); + + // Disk spaces will be merged if fixture parameter is `NONE`. + Bytes totalBytes = GetParam() == NONE ? Megabytes(4096) : Megabytes(2048); + + Bytes bytesDifference = Megabytes(512); + + // Construct a persistent volume which does not use up all disk resources. + Resource volume = createPersistentVolume( + getDiskResource(totalBytes - bytesDifference, 1), + "id1", + "path1", + None(), + frameworkInfo1.principal()); + + Resource difference = getDiskResource(bytesDifference, 1); + + Resource grownVolume = createPersistentVolume( + getDiskResource(totalBytes, 1), + "id1", + "path1", + None(), + frameworkInfo1.principal()); + + Future<vector<Offer>> offersAfterGrow1; + + EXPECT_CALL(sched1, resourceOffers(&driver1, _)) + .WillOnce(FutureArg<1>(&offersAfterGrow1)); + + // We use the filter explicitly here so that the resources will not + // be filtered for 5 seconds (the default). + Filters filters; + filters.set_refuse_seconds(0); + + // Creating the persistent volume will succeed, but growing will fail due to + // ACL. + driver1.acceptOffers( + {offer.id()}, + {CREATE(volume), GROW_VOLUME(volume, difference)}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterGrow1); + ASSERT_FALSE(offersAfterGrow1->empty()); + + offer = offersAfterGrow1->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), DEFAULT_TEST_ROLE), + Resources(offer.resources()).persistentVolumes()); + + EXPECT_TRUE( + Resources(offer.resources()).contains( + allocatedResources(difference, DEFAULT_TEST_ROLE))); + + Future<vector<Offer>> offersAfterShrink1; + + EXPECT_CALL(sched1, resourceOffers(&driver1, _)) + .WillOnce(FutureArg<1>(&offersAfterShrink1)); + + driver1.acceptOffers( + {offer.id()}, + {SHRINK_VOLUME(volume, difference.scalar())}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterShrink1); + ASSERT_FALSE(offersAfterShrink1->empty()); + + offer = offersAfterShrink1->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), DEFAULT_TEST_ROLE), + Resources(offer.resources()).persistentVolumes()); + + driver1.stop(); + driver1.join(); + + // Start the second framework with no principal. + FrameworkInfo frameworkInfo2 = DEFAULT_FRAMEWORK_INFO; + frameworkInfo2.clear_principal(); + frameworkInfo2.set_roles(0, DEFAULT_TEST_ROLE); + + MockScheduler sched2; + MesosSchedulerDriver driver2( + &sched2, frameworkInfo2, master.get()->pid, DEFAULT_CREDENTIAL); + + EXPECT_CALL(sched2, registered(&driver2, _, _)); + + Future<vector<Offer>> offersBeforeGrow2; + EXPECT_CALL(sched2, resourceOffers(&driver2, _)) + .WillOnce(FutureArg<1>(&offersBeforeGrow2)); + + driver2.start(); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersBeforeGrow2); + ASSERT_FALSE(offersBeforeGrow2->empty()); + + offer = offersBeforeGrow2->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), DEFAULT_TEST_ROLE), + Resources(offer.resources()).persistentVolumes()); + + EXPECT_TRUE( + Resources(offer.resources()).contains( + allocatedResources(difference, DEFAULT_TEST_ROLE))); + + Future<vector<Offer>> offersAfterGrow2; + + EXPECT_CALL(sched2, resourceOffers(&driver2, _)) + .WillOnce(FutureArg<1>(&offersAfterGrow2)); + + driver2.acceptOffers( + {offer.id()}, + {GROW_VOLUME(volume, difference)}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterGrow2); + ASSERT_FALSE(offersAfterGrow2->empty()); + offer = offersAfterGrow2->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), DEFAULT_TEST_ROLE), + Resources(offer.resources()).persistentVolumes()); + + Future<vector<Offer>> offersAfterShrink2; + + EXPECT_CALL(sched2, resourceOffers(&driver2, _)) + .WillOnce(FutureArg<1>(&offersAfterShrink2)); + + driver2.acceptOffers( + {offer.id()}, + {SHRINK_VOLUME(volume, difference.scalar())}, + filters); + + Clock::settle(); + Clock::advance(masterFlags.allocation_interval); + + AWAIT_READY(offersAfterShrink2); + ASSERT_FALSE(offersAfterShrink2->empty()); + offer = offersAfterShrink2->at(0); + + EXPECT_EQ( + allocatedResources(Resources(volume), DEFAULT_TEST_ROLE), + Resources(offer.resources()).persistentVolumes()); + + driver2.stop(); + driver2.join(); + + Clock::resume(); +} + + // This test verifies that the slave checkpoints the resources for // persistent volumes to the disk, recovers them upon restart, and // sends them to the master during re-registration.