Repository: mesos Updated Branches: refs/heads/master e758d2460 -> d0d15c5da
Added tests for /reserve and /unreserve HTTP endpoints. Review: https://reviews.apache.org/r/35984 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/d0d15c5d Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/d0d15c5d Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/d0d15c5d Branch: refs/heads/master Commit: d0d15c5dafeec9b70c53454ebbeb664fb2f66d69 Parents: cc9c682 Author: Michael Park <[email protected]> Authored: Wed Aug 5 02:05:07 2015 -0700 Committer: Michael Park <[email protected]> Committed: Wed Sep 9 15:28:29 2015 -0700 ---------------------------------------------------------------------- src/Makefile.am | 1 + src/tests/reservation_endpoints_tests.cpp | 915 +++++++++++++++++++++++++ 2 files changed, 916 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/d0d15c5d/src/Makefile.am ---------------------------------------------------------------------- diff --git a/src/Makefile.am b/src/Makefile.am index 4ef58cd..cea470e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1667,6 +1667,7 @@ mesos_tests_SOURCES = \ tests/registrar_tests.cpp \ tests/repair_tests.cpp \ tests/reservation_tests.cpp \ + tests/reservation_endpoints_tests.cpp \ tests/resource_offers_tests.cpp \ tests/resources_tests.cpp \ tests/scheduler_tests.cpp \ http://git-wip-us.apache.org/repos/asf/mesos/blob/d0d15c5d/src/tests/reservation_endpoints_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/reservation_endpoints_tests.cpp b/src/tests/reservation_endpoints_tests.cpp new file mode 100644 index 0000000..795f1cf --- /dev/null +++ b/src/tests/reservation_endpoints_tests.cpp @@ -0,0 +1,915 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <vector> + +#include <gmock/gmock.h> + +#include <mesos/executor.hpp> +#include <mesos/scheduler.hpp> + +#include <process/future.hpp> +#include <process/gmock.hpp> +#include <process/http.hpp> +#include <process/pid.hpp> + +#include <stout/base64.hpp> +#include <stout/hashmap.hpp> +#include <stout/option.hpp> + +#include "master/flags.hpp" +#include "master/master.hpp" + +#include "tests/mesos.hpp" +#include "tests/utils.hpp" + +using std::string; +using std::vector; + +using mesos::internal::master::Master; +using mesos::internal::slave::Slave; + +using process::Future; +using process::PID; + +using process::http::BadRequest; +using process::http::Conflict; +using process::http::OK; +using process::http::Response; +using process::http::Unauthorized; + +using testing::_; +using testing::DoAll; +using testing::Eq; +using testing::SaveArg; +using testing::Return; + +namespace mesos { +namespace internal { +namespace tests { + + +// Converts a 'RepeatedPtrField<Resource>' to a 'JSON::Array'. +// TODO(mpark): Generalize this to 'JSON::protobuf(RepeatedPtrField<T>)'. +JSON::Array toJSONArray( + const google::protobuf::RepeatedPtrField<Resource>& resources) +{ + JSON::Array array; + + array.values.reserve(resources.size()); + + foreach (const Resource& resource, resources) { + array.values.push_back(JSON::Protobuf(resource)); + } + + return array; +} + + +class ReservationEndpointsTest : public MesosTest +{ +public: + // Set up the master flags such that it allows registration of the framework + // created with 'createFrameworkInfo'. + virtual master::Flags CreateMasterFlags() + { + master::Flags flags = MesosTest::CreateMasterFlags(); + flags.allocation_interval = Milliseconds(50); + flags.roles = createFrameworkInfo().role(); + return flags; + } + + // Returns a FrameworkInfo with role, "role". + FrameworkInfo createFrameworkInfo() + { + FrameworkInfo info = DEFAULT_FRAMEWORK_INFO; + info.set_role("role"); + return info; + } + + hashmap<string, string> createBasicAuthHeaders( + const Credential& credential) const + { + return hashmap<string, string>{{ + "Authorization", + "Basic " + + base64::encode(credential.principal() + ":" + credential.secret()) + }}; + } + + string createRequestBody( + const SlaveID& slaveId, const Resources& resources) const + { + return strings::format( + "slaveId=%s&resources=%s", + slaveId.value(), + toJSONArray(resources)).get(); + } +}; + + +// TODO(mpark): Add tests for ACLs once they are introduced. + + +// This tests that an operator can reserve/unreserve available resources. +TEST_F(ReservationEndpointsTest, AvailableResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); + + Future<vector<Offer>> offers; + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + driver.start(); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + Offer offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(dynamicallyReserved)); + + // The filter to decline the offer "forever". + Filters filtersForever; + filtersForever.set_refuse_seconds(1000); + + // Decline the offer "forever" in order to deallocate resources. + driver.declineOffer(offer.id(), filtersForever); + + Future<Nothing> recoverResources; + EXPECT_CALL(allocator, recoverResources(_, _, _, _)) + .WillOnce(DoAll(InvokeRecoverResources(&allocator), + FutureSatisfy(&recoverResources))); + + AWAIT_READY(recoverResources); + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + response = process::http::post( + master.get(), + "unreserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(unreserved)); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// This tests that an operator can reserve offered resources by rescinding the +// outstanding offers. +TEST_F(ReservationEndpointsTest, ReserveOfferedResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); + + Future<vector<Offer>> offers; + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + driver.start(); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + Offer offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(unreserved)); + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + // Expect an offer to be rescinded! + EXPECT_CALL(sched, offerRescinded(_, _)); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(dynamicallyReserved)); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// This tests that an operator can unreserve offered resources by rescinding the +// outstanding offers. +TEST_F(ReservationEndpointsTest, UnreserveOfferedResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); + + Future<vector<Offer>> offers; + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + driver.start(); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + Offer offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(dynamicallyReserved)); + + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + // Expect an offer to be rescinded! + EXPECT_CALL(sched, offerRescinded(_, _)); + + response = process::http::post( + master.get(), + "unreserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(unreserved)); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// This tests that an operator can reserve a mix of available and offered +// resources by rescinding the outstanding offers. +TEST_F(ReservationEndpointsTest, ReserveAvailableAndOfferedResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + master::Flags masterFlags = CreateMasterFlags(); + // Turn off allocation. We're doing it manually. + masterFlags.allocation_interval = Seconds(1000); + + Try<PID<Master>> master = StartMaster(&allocator, masterFlags); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources available = Resources::parse("cpus:1;mem:128").get(); + Resources offered = Resources::parse("mem:384").get(); + + Resources total = available + offered; + Resources dynamicallyReserved = total.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + driver.start(); + + // We want to get the cluster in a state where 'available' resources are left + // in the allocator, and 'offered' resources are offered to the framework. + // To achieve this state, we perform the following steps: + // (1) Summon an offer containing 'total' = 'available' + 'offered'. + // (2) Launch a "forever-running" task with 'available' resources. + // (3) Summon an offer containing 'offered'. + // (4) Kill the task, which recovers 'available' resources. + + // Summon an offer and expect to receive 'available + offered' resources. + Future<vector<Offer>> offers; + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + ASSERT_EQ(1u, offers.get().size()); + Offer offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(available + offered)); + + // Launch a task on the 'available' resources portion of the offer, which + // recovers 'offered' resources portion. + TaskInfo taskInfo = createTask(offer.slave_id(), available, "sleep 1000"); + + // Expect a TASK_RUNNING status. + EXPECT_CALL(sched, statusUpdate(_, _)); + + Future<Nothing> _statusUpdateAcknowledgement = + FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement); + + Future<Nothing> recoverUnusedResources; + EXPECT_CALL(allocator, recoverResources(_, _, _, _)) + .WillOnce(DoAll(InvokeRecoverResources(&allocator), + FutureSatisfy(&recoverUnusedResources))); + + driver.acceptOffers({offer.id()}, {LAUNCH({taskInfo})}); + + // Wait for TASK_RUNNING update ack and for the resources to be recovered. + AWAIT_READY(_statusUpdateAcknowledgement); + AWAIT_READY(recoverUnusedResources); + + // Summon an offer to receive the 'offered' resources. + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(offered)); + + // Kill the task running on 'available' resources to make it available. + EXPECT_CALL(sched, statusUpdate(_, _)); + + // Wait for the used resources to be recovered. + Future<Resources> availableResources; + EXPECT_CALL(allocator, recoverResources(_, _, _, _)) + .WillOnce(DoAll(InvokeRecoverResources(&allocator), + FutureArg<2>(&availableResources))) + .WillRepeatedly(DoDefault()); + + // Send a KillTask message to the master. + driver.killTask(taskInfo.task_id()); + + EXPECT_TRUE(availableResources.get().contains(available)); + + // At this point, we have 'available' resources in the allocator, and + // 'offered' resources offered to the framework. + + // Expect an offer to be rescinded! + EXPECT_CALL(sched, offerRescinded(_, _)); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + // Summon an offer. + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(dynamicallyReserved)); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// This tests that an operator can unreserve a mix of available and offered +// resources by rescinding the outstanding offers. +TEST_F(ReservationEndpointsTest, UnreserveAvailableAndOfferedResources) +{ + TestAllocator<> allocator; + + master::Flags masterFlags = CreateMasterFlags(); + // Turn off allocation. We're doing it manually. + masterFlags.allocation_interval = Seconds(1000); + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator, masterFlags); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources available = Resources::parse("cpus:1;mem:128").get(); + available = available.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + Resources offered = Resources::parse("mem:384").get(); + offered = offered.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + Resources total = available + offered; + Resources unreserved = total.flatten(); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), total)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + MockScheduler sched; + MesosSchedulerDriver driver( + &sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL); + + EXPECT_CALL(sched, registered(&driver, _, _)); + + driver.start(); + + // We want to get the cluster in a state where 'available' resources are left + // in the allocator, and 'offered' resources are offered to the framework. + // To achieve this state, we perform the following steps: + // (1) Summon an offer containing 'total' = 'available' + 'offered'. + // (2) Launch a "forever-running" task with 'available' resources. + // (3) Summon an offer containing 'offered'. + // (4) Kill the task, which recovers 'available' resources. + + // Summon an offer and expect to receive 'available + offered' resources. + Future<vector<Offer>> offers; + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + ASSERT_EQ(1u, offers.get().size()); + Offer offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(available + offered)); + + // Launch a task on the 'available' resources portion of the offer, which + // recovers 'offered' resources portion. + TaskInfo taskInfo = createTask(offer.slave_id(), available, "sleep 1000"); + + // Expect a TASK_RUNNING status. + EXPECT_CALL(sched, statusUpdate(_, _)); + + Future<Nothing> _statusUpdateAcknowledgement = + FUTURE_DISPATCH(_, &Slave::_statusUpdateAcknowledgement); + + Future<Nothing> recoverUnusedResources; + EXPECT_CALL(allocator, recoverResources(_, _, _, _)) + .WillOnce(DoAll(InvokeRecoverResources(&allocator), + FutureSatisfy(&recoverUnusedResources))); + + driver.acceptOffers({offer.id()}, {LAUNCH({taskInfo})}); + + // Wait for TASK_RUNNING update ack and for the resources to be recovered. + AWAIT_READY(_statusUpdateAcknowledgement); + AWAIT_READY(recoverUnusedResources); + + // Summon an offer to receive the 'offered' resources. + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(offered)); + + // Kill the task running on 'available' resources to make it available. + EXPECT_CALL(sched, statusUpdate(_, _)); + + // Wait for the used resources to be recovered. + Future<Resources> availableResources; + EXPECT_CALL(allocator, recoverResources(_, _, _, _)) + .WillOnce(DoAll(InvokeRecoverResources(&allocator), + FutureArg<2>(&availableResources))) + .WillRepeatedly(DoDefault()); + + // Send a KillTask message to the master. + driver.killTask(taskInfo.task_id()); + + EXPECT_TRUE(availableResources.get().contains(available)); + + // At this point, we have 'available' resources in the allocator, and + // 'offered' resources offered to the framework. + + // Expect an offer to be rescinded! + EXPECT_CALL(sched, offerRescinded(_, _)); + + response = process::http::post( + master.get(), + "unreserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), total)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); + + // Summon an offer. + EXPECT_CALL(sched, resourceOffers(&driver, _)) + .WillOnce(FutureArg<1>(&offers)); + + driver.reviveOffers(); + + AWAIT_READY(offers); + + ASSERT_EQ(1u, offers.get().size()); + offer = offers.get()[0]; + + EXPECT_TRUE(Resources(offer.resources()).contains(unreserved)); + + driver.stop(); + driver.join(); + + Shutdown(); +} + + +// This tests that an attempt to reserve/unreserve more resources than available +// results in a 'Conflict' HTTP error. +TEST_F(ReservationEndpointsTest, InsufficientResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources unreserved = Resources::parse("cpus:4;mem:4096").get(); + Resources dynamicallyReserved = unreserved.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + hashmap<string, string> headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); + string body = createRequestBody(slaveId.get(), dynamicallyReserved); + + Future<Response> response = + process::http::post(master.get(), "reserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(Conflict().status, response); + + response = process::http::post(master.get(), "unreserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(Conflict().status, response); + + Shutdown(); +} + + +// This tests that an attempt to reserve with no authorization header results in +// a 'Unauthorized' HTTP error. +TEST_F(ReservationEndpointsTest, NoHeader) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + FrameworkInfo frameworkInfo = createFrameworkInfo(); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + frameworkInfo.role(), + createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + None(), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ( + Unauthorized("Mesos master").status, + response); + + response = process::http::post( + master.get(), + "unreserve", + None(), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ( + Unauthorized("Mesos master").status, + response); + + Shutdown(); +} + + +// This tests that an attempt to reserve with bad credentials results in a +// 'Unauthorized' HTTP error. +TEST_F(ReservationEndpointsTest, BadCredentials) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + Credential credential; + credential.set_principal("bad-principal"); + credential.set_secret("bad-secret"); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + "role", createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + hashmap<string, string> headers = createBasicAuthHeaders(credential); + string body = createRequestBody(slaveId.get(), dynamicallyReserved); + + Future<Response> response = + process::http::post(master.get(), "reserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ( + Unauthorized("Mesos master").status, + response); + + response = process::http::post(master.get(), "unreserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ( + Unauthorized("Mesos master").status, + response); + + Shutdown(); +} + + +// This tests that an attempt to reserve with no 'slaveId' results in a +// 'BadRequest' HTTP error. +TEST_F(ReservationEndpointsTest, NoSlaveId) +{ + Try<PID<Master>> master = StartMaster(); + ASSERT_SOME(master); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = unreserved.flatten( + "role", createReservationInfo(DEFAULT_CREDENTIAL.principal())); + + hashmap<string, string> headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); + string body = "resources=" + stringify(toJSONArray(dynamicallyReserved)); + + Future<Response> response = + process::http::post(master.get(), "reserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); + + response = process::http::post(master.get(), "unreserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); + + Shutdown(); +} + + +// This tests that an attempt to reserve with no 'resources' results in a +// 'BadRequest' HTTP error. +TEST_F(ReservationEndpointsTest, NoResources) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + hashmap<string, string> headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); + string body = "slaveId=" + slaveId.get().value(); + + Future<Response> response = + process::http::post(master.get(), "reserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); + + response = process::http::post(master.get(), "unreserve", headers, body); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); + + Shutdown(); +} + + +// This tests that an attempt to reserve with a non-matching principal results +// in a 'BadRequest' HTTP error. +TEST_F(ReservationEndpointsTest, NonMatchingPrincipal) +{ + TestAllocator<> allocator; + + EXPECT_CALL(allocator, initialize(_, _, _)); + + Try<PID<Master>> master = StartMaster(&allocator); + ASSERT_SOME(master); + + Future<SlaveID> slaveId; + EXPECT_CALL(allocator, addSlave(_, _, _, _)) + .WillOnce(DoAll(InvokeAddSlave(&allocator), + FutureArg<0>(&slaveId))); + + Try<PID<Slave>> slave = StartSlave(); + ASSERT_SOME(slave); + + Resources unreserved = Resources::parse("cpus:1;mem:512").get(); + Resources dynamicallyReserved = + unreserved.flatten("role", createReservationInfo("badPrincipal")); + + Future<Response> response = process::http::post( + master.get(), + "reserve", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + createRequestBody(slaveId.get(), dynamicallyReserved)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); + + Shutdown(); +} + +} // namespace tests { +} // namespace internal { +} // namespace mesos {
