blob: fc3a72894631279460ee7971a4627d73c3d8c351 [file] [log] [blame]
// 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 <mesos/http.hpp>
#include <mesos/roles.hpp>
#include <process/pid.hpp>
#include "tests/mesos.hpp"
using mesos::internal::master::Master;
using mesos::internal::slave::Slave;
using std::string;
using std::vector;
using google::protobuf::RepeatedPtrField;
using process::Future;
using process::PID;
using process::http::OK;
using process::http::Response;
using testing::AtMost;
namespace mesos {
namespace internal {
namespace tests {
class RoleTest : public MesosTest {};
// This test checks that a framework cannot register with a role that
// is not in the configured list.
TEST_F(RoleTest, BadRegister)
{
FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
frameworkInfo.set_role("invalid");
master::Flags masterFlags = CreateMasterFlags();
masterFlags.roles = "foo,bar";
Try<PID<Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
MockScheduler sched;
MesosSchedulerDriver driver(
&sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
Future<Nothing> error;
EXPECT_CALL(sched, error(&driver, _))
.WillOnce(FutureSatisfy(&error));
driver.start();
// Scheduler should get error message from the master.
AWAIT_READY(error);
driver.stop();
driver.join();
Shutdown();
}
// This test checks that when using implicit roles, a framework can
// register with a new role, make a dynamic reservation, and create a
// persistent volume.
TEST_F(RoleTest, ImplicitRoleRegister)
{
master::Flags masterFlags = CreateMasterFlags();
masterFlags.allocation_interval = Milliseconds(50);
Try<PID<Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
slave::Flags slaveFlags = CreateSlaveFlags();
slaveFlags.resources = "cpus:1;mem:512;disk:1024";
Try<PID<Slave>> slave = StartSlave(slaveFlags);
ASSERT_SOME(slave);
FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
frameworkInfo.set_role("new-role-name");
MockScheduler sched;
MesosSchedulerDriver driver(
&sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
// 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);
Resources unreserved = Resources::parse("disk:1024").get();
Resources dynamicallyReserved = unreserved.flatten(
frameworkInfo.role(), createReservationInfo(frameworkInfo.principal()));
// We use this to capture offers from `resourceOffers`.
Future<vector<Offer>> offers;
EXPECT_CALL(sched, registered(&driver, _, _));
// The expectation for the first offer.
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
driver.start();
// In the first offer, expect an offer with unreserved resources.
AWAIT_READY(offers);
ASSERT_EQ(1u, offers.get().size());
Offer offer = offers.get()[0];
EXPECT_TRUE(Resources(offer.resources()).contains(unreserved));
EXPECT_FALSE(Resources(offer.resources()).contains(dynamicallyReserved));
// The expectation for the next offer.
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
// Reserve the resources.
driver.acceptOffers({offer.id()}, {RESERVE(dynamicallyReserved)}, filters);
// In the next offer, expect an offer with reserved resources.
AWAIT_READY(offers);
ASSERT_EQ(1u, offers.get().size());
offer = offers.get()[0];
EXPECT_TRUE(Resources(offer.resources()).contains(dynamicallyReserved));
EXPECT_FALSE(Resources(offer.resources()).contains(unreserved));
Resources volume = createPersistentVolume(
Megabytes(64),
frameworkInfo.role(),
"id1",
"path1",
frameworkInfo.principal());
// The expectation for the next offer.
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
driver.acceptOffers({offer.id()}, {CREATE(volume)}, filters);
// In the next offer, expect an offer with a persistent volume.
AWAIT_READY(offers);
ASSERT_EQ(1u, offers.get().size());
offer = offers.get()[0];
EXPECT_TRUE(Resources(offer.resources()).contains(volume));
EXPECT_FALSE(Resources(offer.resources()).contains(dynamicallyReserved));
EXPECT_FALSE(Resources(offer.resources()).contains(unreserved));
driver.stop();
driver.join();
Shutdown();
}
// This test checks that when using implicit roles, a static
// reservation for a new role can be made and used to launch a task.
TEST_F(RoleTest, ImplicitRoleStaticReservation)
{
master::Flags masterFlags = CreateMasterFlags();
masterFlags.allocation_interval = Milliseconds(50);
Try<PID<Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
MockExecutor exec(DEFAULT_EXECUTOR_ID);
slave::Flags slaveFlags = CreateSlaveFlags();
slaveFlags.resources = "cpus(role):1;mem(role):512";
Try<PID<Slave>> slave = StartSlave(&exec, slaveFlags);
ASSERT_SOME(slave);
FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
frameworkInfo.set_role("role");
MockScheduler sched;
MesosSchedulerDriver driver(
&sched, frameworkInfo, master.get(), DEFAULT_CREDENTIAL);
// 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);
Resources staticallyReserved =
Resources::parse(slaveFlags.resources.get()).get();
// We use this to capture offers from `resourceOffers`.
Future<vector<Offer>> offers;
EXPECT_CALL(sched, registered(&driver, _, _));
// The expectation for the first offer.
EXPECT_CALL(sched, resourceOffers(&driver, _))
.WillOnce(FutureArg<1>(&offers));
driver.start();
AWAIT_READY(offers);
ASSERT_EQ(1u, offers.get().size());
Offer offer = offers.get()[0];
EXPECT_TRUE(Resources(offer.resources()).contains(staticallyReserved));
// Create a task to launch with the resources of `staticallyReserved`.
TaskInfo taskInfo =
createTask(offer.slave_id(), staticallyReserved, "exit 1", exec.id);
EXPECT_CALL(exec, registered(_, _, _, _));
Future<TaskInfo> launchTask;
EXPECT_CALL(exec, launchTask(_, _))
.WillOnce(FutureArg<1>(&launchTask));
driver.acceptOffers({offer.id()}, {LAUNCH({taskInfo})}, filters);
AWAIT_READY(launchTask);
EXPECT_CALL(exec, shutdown(_))
.Times(AtMost(1));
driver.stop();
driver.join();
Shutdown();
}
// This test checks that the "/roles" endpoint returns the expected
// information when there are no active roles.
TEST_F(RoleTest, EndpointEmpty)
{
Try<PID<Master>> master = StartMaster();
ASSERT_SOME(master);
Future<Response> response = process::http::get(master.get(), "roles");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
<< response.get().body;
AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
Try<JSON::Value> parse = JSON::parse(response.get().body);
ASSERT_SOME(parse);
Try<JSON::Value> expected = JSON::parse(
"{"
" \"roles\": ["
" {"
" \"frameworks\": [],"
" \"name\": \"*\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" }"
" ]"
"}");
ASSERT_SOME(expected);
EXPECT_EQ(expected.get(), parse.get());
Shutdown();
}
// This test checks that the "/roles" endpoint returns the expected
// information when there are configured weights and explicit roles,
// but no registered frameworks.
TEST_F(RoleTest, EndpointNoFrameworks)
{
master::Flags masterFlags = CreateMasterFlags();
masterFlags.roles = "role1,role2";
masterFlags.weights = "role1=5";
Try<PID<Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
Future<Response> response = process::http::get(master.get(), "roles");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
<< response.get().body;
AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
Try<JSON::Value> parse = JSON::parse(response.get().body);
ASSERT_SOME(parse);
Try<JSON::Value> expected = JSON::parse(
"{"
" \"roles\": ["
" {"
" \"frameworks\": [],"
" \"name\": \"*\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" },"
" {"
" \"frameworks\": [],"
" \"name\": \"role1\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 5.0"
" },"
" {"
" \"frameworks\": [],"
" \"name\": \"role2\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" }"
" ]"
"}");
ASSERT_SOME(expected);
EXPECT_EQ(expected.get(), parse.get());
Shutdown();
}
// This test checks that when using implicit roles, the "/roles"
// endpoint shows roles that have a configured weight even if they
// have no registered frameworks.
TEST_F(RoleTest, EndpointImplicitRolesWeights)
{
master::Flags masterFlags = CreateMasterFlags();
masterFlags.weights = "roleX=5,roleY=4";
Try<PID<Master>> master = StartMaster(masterFlags);
ASSERT_SOME(master);
FrameworkInfo frameworkInfo1 = DEFAULT_FRAMEWORK_INFO;
frameworkInfo1.set_role("roleX");
MockScheduler sched1;
MesosSchedulerDriver driver1(
&sched1, frameworkInfo1, master.get(), DEFAULT_CREDENTIAL);
Future<FrameworkID> frameworkId1;
EXPECT_CALL(sched1, registered(&driver1, _, _))
.WillOnce(FutureArg<1>(&frameworkId1));;
driver1.start();
FrameworkInfo frameworkInfo2 = DEFAULT_FRAMEWORK_INFO;
frameworkInfo2.set_role("roleZ");
MockScheduler sched2;
MesosSchedulerDriver driver2(
&sched2, frameworkInfo2, master.get(), DEFAULT_CREDENTIAL);
Future<FrameworkID> frameworkId2;
EXPECT_CALL(sched2, registered(&driver2, _, _))
.WillOnce(FutureArg<1>(&frameworkId2));;
driver2.start();
AWAIT_READY(frameworkId1);
AWAIT_READY(frameworkId2);
Future<Response> response = process::http::get(master.get(), "roles");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
<< response.get().body;
AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
Try<JSON::Value> parse = JSON::parse(response.get().body);
ASSERT_SOME(parse);
Try<JSON::Value> expected = JSON::parse(
"{"
" \"roles\": ["
" {"
" \"frameworks\": [],"
" \"name\": \"*\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" },"
" {"
" \"frameworks\": [\"" + frameworkId1.get().value() + "\"],"
" \"name\": \"roleX\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 5.0"
" },"
" {"
" \"frameworks\": [],"
" \"name\": \"roleY\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 4.0"
" },"
" {"
" \"frameworks\": [\"" + frameworkId2.get().value() + "\"],"
" \"name\": \"roleZ\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" }"
" ]"
"}");
ASSERT_SOME(expected);
EXPECT_EQ(expected.get(), parse.get());
driver1.stop();
driver1.join();
driver2.stop();
driver2.join();
Shutdown();
}
// This test checks that when using implicit roles, the "/roles"
// endpoint shows roles that have a configured quota even if they have
// no registered frameworks.
TEST_F(RoleTest, EndpointImplicitRolesQuotas)
{
Try<PID<Master>> master = StartMaster();
ASSERT_SOME(master);
Resources quotaResources = Resources::parse("cpus:1;mem:512").get();
const RepeatedPtrField<Resource>& jsonQuotaResources =
static_cast<const RepeatedPtrField<Resource>&>(quotaResources);
// Send a quota request for a new role name. Note that we set
// "force" to true because we're setting a quota that can't
// currently be satisfied by the resources in the cluster (because
// there are no slaves registered).
string quotaRequestBody = strings::format(
"{\"role\":\"non-existent-role\",\"guarantee\":%s,\"force\":true}",
JSON::protobuf(jsonQuotaResources)).get();
Future<Response> quotaResponse = process::http::post(
master.get(),
"quota",
createBasicAuthHeaders(DEFAULT_CREDENTIAL),
quotaRequestBody);
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, quotaResponse)
<< quotaResponse.get().body;
Future<Response> rolesResponse = process::http::get(master.get(), "roles");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, rolesResponse)
<< rolesResponse.get().body;
AWAIT_EXPECT_RESPONSE_HEADER_EQ(
APPLICATION_JSON, "Content-Type", rolesResponse);
Try<JSON::Value> parse = JSON::parse(rolesResponse.get().body);
ASSERT_SOME(parse);
Try<JSON::Value> expected = JSON::parse(
"{"
" \"roles\": ["
" {"
" \"frameworks\": [],"
" \"name\": \"*\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" },"
" {"
" \"frameworks\": [],"
" \"name\": \"non-existent-role\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" }"
" ]"
"}");
ASSERT_SOME(expected);
EXPECT_EQ(expected.get(), parse.get());
// Remove the quota, and check that the role no longer appears in
// the "/roles" endpoint.
Future<Response> deleteResponse = process::http::requestDelete(
master.get(),
"quota/non-existent-role",
createBasicAuthHeaders(DEFAULT_CREDENTIAL));
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, deleteResponse)
<< deleteResponse.get().body;
rolesResponse = process::http::get(master.get(), "roles");
AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, rolesResponse)
<< rolesResponse.get().body;
AWAIT_EXPECT_RESPONSE_HEADER_EQ(
APPLICATION_JSON, "Content-Type", rolesResponse);
parse = JSON::parse(rolesResponse.get().body);
ASSERT_SOME(parse);
expected = JSON::parse(
"{"
" \"roles\": ["
" {"
" \"frameworks\": [],"
" \"name\": \"*\","
" \"resources\": {"
" \"cpus\": 0,"
" \"disk\": 0,"
" \"mem\": 0"
" },"
" \"weight\": 1.0"
" }"
" ]"
"}");
ASSERT_SOME(expected);
EXPECT_EQ(expected.get(), parse.get());
Shutdown();
}
// This tests the parse function of roles.
TEST(RolesTest, Parsing)
{
vector<string> v = {"abc", "edf", "hgi"};
Try<vector<string>> r1 = roles::parse("abc,edf,hgi");
EXPECT_SOME_EQ(v, r1);
Try<vector<string>> r2 = roles::parse("abc,edf,hgi,");
EXPECT_SOME_EQ(v, r2);
Try<vector<string>> r3 = roles::parse(",abc,edf,hgi");
EXPECT_SOME_EQ(v, r3);
Try<vector<string>> r4 = roles::parse("abc,,edf,hgi");
EXPECT_SOME_EQ(v, r4);
EXPECT_ERROR(roles::parse("foo,.,*"));
}
// This tests the validate functions of roles. Keep in mind that
// roles::validate returns Option<Error>, so it will return None() when
// validation succeeds, or Error() when validation fails.
TEST(RolesTest, Validate)
{
EXPECT_NONE(roles::validate("foo"));
EXPECT_NONE(roles::validate("123"));
EXPECT_NONE(roles::validate("foo_123"));
EXPECT_NONE(roles::validate("*"));
EXPECT_NONE(roles::validate("foo-bar"));
EXPECT_NONE(roles::validate("foo.bar"));
EXPECT_NONE(roles::validate("foo..bar"));
EXPECT_SOME(roles::validate(""));
EXPECT_SOME(roles::validate("."));
EXPECT_SOME(roles::validate(".."));
EXPECT_SOME(roles::validate("-foo"));
EXPECT_SOME(roles::validate("/"));
EXPECT_SOME(roles::validate("/foo"));
EXPECT_SOME(roles::validate("foo bar"));
EXPECT_SOME(roles::validate("foo\tbar"));
EXPECT_SOME(roles::validate("foo\nbar"));
EXPECT_NONE(roles::validate({"foo", "bar", "*"}));
EXPECT_SOME(roles::validate({"foo", ".", "*"}));
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {