You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by me...@apache.org on 2016/03/14 12:21:03 UTC
mesos git commit: Added tests for /weights endpoint.
Repository: mesos
Updated Branches:
refs/heads/master 79e28b614 -> 69b2ad528
Added tests for /weights endpoint.
Review: https://reviews.apache.org/r/41790/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/69b2ad52
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/69b2ad52
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/69b2ad52
Branch: refs/heads/master
Commit: 69b2ad528dd79979a8ee113a8edbbab2669e32e6
Parents: 79e28b6
Author: Yongqiao Wang <yq...@cn.ibm.com>
Authored: Mon Mar 14 03:19:07 2016 -0700
Committer: Adam B <ad...@mesosphere.io>
Committed: Mon Mar 14 03:19:07 2016 -0700
----------------------------------------------------------------------
src/Makefile.am | 1 +
src/tests/dynamic_weights_tests.cpp | 632 +++++++++++++++++++++++++++++++
2 files changed, 633 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/69b2ad52/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index f2a592d..7ee5a65 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1829,6 +1829,7 @@ mesos_tests_SOURCES = \
tests/cram_md5_authentication_tests.cpp \
tests/credentials_tests.cpp \
tests/disk_quota_tests.cpp \
+ tests/dynamic_weights_tests.cpp \
tests/environment.cpp \
tests/examples_tests.cpp \
tests/exception_tests.cpp \
http://git-wip-us.apache.org/repos/asf/mesos/blob/69b2ad52/src/tests/dynamic_weights_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/dynamic_weights_tests.cpp b/src/tests/dynamic_weights_tests.cpp
new file mode 100644
index 0000000..a95663f
--- /dev/null
+++ b/src/tests/dynamic_weights_tests.cpp
@@ -0,0 +1,632 @@
+// 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/mesos.hpp>
+
+#include <process/future.hpp>
+#include <process/http.hpp>
+#include <process/id.hpp>
+#include <process/pid.hpp>
+
+#include <stout/format.hpp>
+#include <stout/protobuf.hpp>
+#include <stout/strings.hpp>
+
+#include "master/flags.hpp"
+#include "master/master.hpp"
+
+#include "slave/slave.hpp"
+
+#include "tests/allocator.hpp"
+#include "tests/mesos.hpp"
+
+using google::protobuf::RepeatedPtrField;
+
+using mesos::internal::master::Master;
+
+using mesos::internal::slave::Slave;
+
+using process::Future;
+using process::PID;
+using process::UPID;
+
+using process::http::BadRequest;
+using process::http::CaseInsensitiveEqual;
+using process::http::CaseInsensitiveHash;
+using process::http::Conflict;
+using process::http::Forbidden;
+using process::http::Headers;
+using process::http::OK;
+using process::http::Response;
+using process::http::Unauthorized;
+
+using std::string;
+using std::vector;
+
+using testing::_;
+using testing::DoAll;
+using testing::Eq;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+// The dynamic weights tests in this file are split into logical groups:
+// * Request validation tests.
+// * Sanity check tests.
+// * Dynamic weights functionality tests.
+// * Failover and recovery tests.
+// * Authentication and authorization tests.
+class DynamicWeightsTest : public MesosTest
+{
+protected:
+ DynamicWeightsTest() {}
+
+ // Create WeightInfos from the specified weights flag.
+ RepeatedPtrField<WeightInfo> createWeightInfos(const string& weightsFlag)
+ {
+ RepeatedPtrField<WeightInfo> infos;
+ vector<string> tokens = strings::tokenize(weightsFlag, ",");
+ foreach (const string& token, tokens) {
+ vector<string> pair = strings::tokenize(token, "=");
+ EXPECT_EQ(2u, pair.size());
+ double weight = atof(pair[1].c_str());
+ WeightInfo weightInfo;
+ weightInfo.set_role(pair[0]);
+ weightInfo.set_weight(weight);
+ infos.Add()->CopyFrom(weightInfo);
+ }
+
+ return infos;
+ }
+
+ // Generates a weights update request from the specified weights.
+ string createUpdateRequestBody(
+ const RepeatedPtrField<WeightInfo>& infos) const
+ {
+ const string request = strings::format(
+ "%s",
+ JSON::protobuf(infos)).get();
+
+ return request;
+ }
+
+ void checkWithRolesEndpoint(
+ const Try<PID<Master>>& master,
+ const Option<string>& weights = None())
+ {
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "GET",
+ false,
+ "roles",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL)));
+
+ 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::Null();
+
+ if (weights.isNone()) {
+ expected = JSON::parse(
+ "{"
+ " \"roles\": ["
+ " {"
+ " \"frameworks\": [],"
+ " \"name\": \"*\","
+ " \"resources\": {"
+ " \"cpus\": 0,"
+ " \"disk\": 0,"
+ " \"mem\": 0"
+ " },"
+ " \"weight\": 1.0"
+ " }"
+ " ]"
+ "}");
+ } else if (weights == DEFAULT_WEIGHTS) {
+ 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\": 1.0"
+ " },"
+ " {"
+ " \"frameworks\": [],"
+ " \"name\": \"role2\","
+ " \"resources\": {"
+ " \"cpus\": 0,"
+ " \"disk\": 0,"
+ " \"mem\": 0"
+ " },"
+ " \"weight\": 1.0"
+ " }"
+ " ]"
+ "}");
+ } else if (weights == UPDATED_WEIGHTS) {
+ 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\": 2.0"
+ " },"
+ " {"
+ " \"frameworks\": [],"
+ " \"name\": \"role2\","
+ " \"resources\": {"
+ " \"cpus\": 0,"
+ " \"disk\": 0,"
+ " \"mem\": 0"
+ " },"
+ " \"weight\": 4.0"
+ " }"
+ " ]"
+ "}");
+ } else {
+ expected = Error("Unexpected weights string.");
+ }
+
+ ASSERT_SOME(expected);
+ EXPECT_EQ(expected.get(), parse.get());
+ }
+
+protected:
+ const string ROLE1 = "role1";
+ const string ROLE2 = "role2";
+ const string DEFAULT_WEIGHTS = "role1=1.0,role2=1.0";
+ const string UPDATED_WEIGHTS = "role1=2.0,role2=4.0";
+};
+
+
+// Update weights requests with invalid JSON structure
+// should return a '400 Bad Request'.
+TEST_F(DynamicWeightsTest, PutInvalidRequest)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Tests whether an update weights request with invalid JSON fails.
+ string badRequest =
+ "[{"
+ " \"weight\":3.2,"
+ " \"role\""
+ "}]";
+
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ badRequest));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ // Tests whether an update weights request with an invalid field fails.
+ // In this case, the correct field name should be 'role'.
+ badRequest =
+ "[{"
+ " \"weight\":3.2,"
+ " \"invalidField\":\"role1\""
+ "}]";
+
+ response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ badRequest));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// A update weights request with zero value
+// should return a '400 Bad Request'.
+TEST_F(DynamicWeightsTest, ZeroWeight)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Send a weight update request to update the weight of 'role1' to 0.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos("role1=0"))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// A update weights request with negative value
+// should return a '400 Bad Request'.
+TEST_F(DynamicWeightsTest, NegativeWeight)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Send a weight update request to update the weight of 'role1' to -2.0.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos("role1=-2.0"))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// A update weights request with non-numeric value
+// should return a '400 Bad Request'.
+TEST_F(DynamicWeightsTest, NonNumericWeight)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Send a weight update request to update the weight of 'role1' to 'two'
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos("role1=two"))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// Updates must be explicit about what role they are updating,
+// and a missing role request should return a '400 Bad Request'.
+TEST_F(DynamicWeightsTest, MissingRole)
+{
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Send a missing role update request.
+ Future<Response> response1 = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ "weights=[{\"weight\":2.0}]"));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response1)
+ << response1.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ // Send an empty role (only a space) update request.
+ Future<Response> response2 = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos(" =2.0"))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response2)
+ << response2.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// Verifies that an update request for an unknown role (not specified
+// in --roles flag) is rejected when using the explicit roles (--roles flag
+// is specified when master is started).
+TEST_F(DynamicWeightsTest, UnknownRole)
+{
+ // Specify --roles whitelist when starting master.
+ master::Flags flags = MesosTest::CreateMasterFlags();
+ flags.roles = strings::join(",", ROLE1, ROLE2);
+ Try<PID<Master>> master = StartMaster(flags);
+ ASSERT_SOME(master);
+
+ // Send a weight update request for 'unknown' role.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos("unknown=3.0"))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master, DEFAULT_WEIGHTS);
+
+ ShutdownMasters();
+}
+
+
+// Verifies that a weights update request for a whitelisted role
+// (contained in --roles flag) succeeds when using the explicit
+// roles (--roles flag is specified when master is started).
+TEST_F(DynamicWeightsTest, UpdateWeightsWithExplictRoles)
+{
+ // Specify --roles whitelist when starting master.
+ master::Flags flags = MesosTest::CreateMasterFlags();
+ flags.roles = strings::join(",", ROLE1, ROLE2);
+ Try<PID<Master>> master = StartMaster(flags);
+ ASSERT_SOME(master);
+
+ checkWithRolesEndpoint(master, DEFAULT_WEIGHTS);
+
+ // Send a weight update request for the specified roles in UPDATED_WEIGHTS.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master, UPDATED_WEIGHTS);
+
+ ShutdownMasters();
+}
+
+
+// Checks that an update weight request is rejected
+// for unauthenticated principals.
+TEST_F(DynamicWeightsTest, UnauthenticatedUpdateWeightRequest)
+{
+ // The master is configured so that only requests from `DEFAULT_CREDENTIAL`
+ // are authenticated.
+ Try<PID<Master>> master = StartMaster();
+ ASSERT_SOME(master);
+
+ Credential credential;
+ credential.set_principal("unknown-principal");
+ credential.set_secret("test-secret");
+
+ // Send a weight update request for the specified roles in UPDATED_WEIGHTS.
+ Future<Response> response1 = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(credential),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response1)
+ << response1.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ // The absence of credentials leads to authentication failure as well.
+ Future<Response> response2 = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ None(),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response2)
+ << response2.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+
+// Checks that an authorized principal can update weight with implicit roles.
+TEST_F(DynamicWeightsTest, AuthorizedWeightUpdateRequest)
+{
+ // Setup ACLs so that the default principal (DEFAULT_CREDENTIAL.principal())
+ // can update weight for `ROLE1` and `ROLE2`.
+ ACLs acls;
+ acls.set_permissive(false); // Restrictive.
+ mesos::ACL::UpdateWeights* acl = acls.add_update_weights();
+ acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+ acl->mutable_roles()->add_values(ROLE1);
+ acl->mutable_roles()->add_values(ROLE2);
+
+ master::Flags masterFlags = CreateMasterFlags();
+ masterFlags.acls = acls;
+
+ Try<PID<Master>> master = StartMaster(masterFlags);
+ ASSERT_SOME(master);
+
+ // Send a weight update request for the specified roles in UPDATED_WEIGHTS.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master, UPDATED_WEIGHTS);
+
+ ShutdownMasters();
+}
+
+
+// Checks that update weight requests can be authorized without authentication
+// if an authorization rule exists that applies to anyone. The authorizer
+// will map the absence of a principal to "ANY".
+TEST_F(DynamicWeightsTest, AuthorizedUpdateWeightRequestWithoutPrincipal)
+{
+ // Setup ACLs so that any principal can update weight
+ // for `ROLE1` and `ROLE2`.
+ ACLs acls;
+ acls.set_permissive(false); // Restrictive.
+ mesos::ACL::UpdateWeights* acl = acls.add_update_weights();
+ acl->mutable_principals()->set_type(mesos::ACL::Entity::ANY);
+ acl->mutable_roles()->add_values(ROLE1);
+ acl->mutable_roles()->add_values(ROLE2);
+
+ // Disable authentication and set acls.
+ master::Flags masterFlags = CreateMasterFlags();
+ masterFlags.authenticate_http = false;
+ masterFlags.acls = acls;
+
+ Try<PID<Master>> master = StartMaster(masterFlags);
+ ASSERT_SOME(master);
+
+ // Send a weight update request for the specified roles in UPDATED_WEIGHTS.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ None(),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master, UPDATED_WEIGHTS);
+
+ ShutdownMasters();
+}
+
+
+// Checks that an unauthorized principal cannot update weight.
+TEST_F(DynamicWeightsTest, UnauthorizedWeightUpdateRequest)
+{
+ // Set ACLs to be non-permissive by default so that no principal can
+ // update weight for `ROLE1` and `ROLE2`.
+ ACLs acls;
+ acls.set_permissive(false); // Restrictive.
+
+ master::Flags masterFlags = CreateMasterFlags();
+ masterFlags.acls = acls;
+
+ Try<PID<Master>> master = StartMaster(masterFlags);
+ ASSERT_SOME(master);
+
+ // Send a weight update request for the specified roles in UPDATED_WEIGHTS.
+ Future<Response> response = process::http::request(
+ process::http::createRequest(
+ master.get(),
+ "PUT",
+ false,
+ "weights",
+ createBasicAuthHeaders(DEFAULT_CREDENTIAL),
+ createUpdateRequestBody(createWeightInfos(UPDATED_WEIGHTS))));
+
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response)
+ << response.get().body;
+
+ checkWithRolesEndpoint(master);
+
+ ShutdownMasters();
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {