// Testing get without authentication and with bad credentials. TEST_F(HealthTest, ObserveEndpointBadAuthentication) { // Set up a master with authentication required. // Note that the default master test flags enable HTTP authentication. Try<PID<Master>> master = StartMaster(); ASSERT_SOME(master); // Headers for POSTs to maintenance endpoints without authentication. process::http::Headers unauthenticatedHeaders; unauthenticatedHeaders["Content-Type"] = "application/json"; // Bad credentials which should fail authentication. Credential badCredential; badCredential.set_principal("badPrincipal"); badCredential.set_secret("badSecret"); // Headers for POSTs to maintenance endpoints with bad authentication. process::http::Headers badAuthenticationHeaders; badAuthenticationHeaders = createBasicAuthHeaders(badCredential); badAuthenticationHeaders["Content-Type"] = "application/json"; // Post to observe without authentication. Future<Response> response = process::http::post( master.get(), "observe", unauthenticatedHeaders, "monitor=a&hosts=b&level=Ok"); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); // Get request without authentication. response = process::http::get(master.get(), "observe"); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); // Post to observe with bad authentication. response = process::http::post( master.get(), "observe", badAuthenticationHeaders, "monitor=a&hosts=b&level=Ok"); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); // Get request with bad authentication. response = process::http::get( master.get(), "observe", None(), createBasicAuthHeaders(badCredential)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); Shutdown(); }
// Testing route without frameworkId value. TEST_F(TeardownTest, NoFrameworkId) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); ASSERT_EQ(DRIVER_RUNNING, driver.start()); AWAIT_READY(frameworkId); Future<Response> response = process::http::post( master.get()->pid, "teardown", createBasicAuthHeaders(DEFAULT_CREDENTIAL), ""); AWAIT_READY(response); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); driver.stop(); driver.join(); }
// This test verifies that the client will receive a `BadRequest` response if it // includes a stream ID header with a subscribe call. TEST_P(SchedulerHttpApiTest, SubscribeWithStreamId) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; headers["Mesos-Stream-Id"] = UUID::random().toString(); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); }
// Testing route with bad credentials. TEST_F(TeardownTest, BadCredentials) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); ASSERT_EQ(DRIVER_RUNNING, driver.start()); AWAIT_READY(frameworkId); Credential badCredential; badCredential.set_principal("badPrincipal"); badCredential.set_secret("badSecret"); Future<Response> response = process::http::post( master.get()->pid, "teardown", createBasicAuthHeaders(badCredential), "frameworkId=" + frameworkId.get().value()); AWAIT_READY(response); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Unauthorized({}).status, response); driver.stop(); driver.join(); }
// This test sets an unsupported media type as Content-Type. This // should result in a 415 (UnsupportedMediaType) response. TEST_P(SchedulerHttpApiTest, UnsupportedContentMediaType) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); const string unknownMediaType = "application/unknown-media-type"; Future<Response> response = process::http::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), unknownMediaType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(UnsupportedMediaType().status, response); }
TEST_P(SchedulerHttpApiTest, DefaultAccept) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = "*/*"; // Only subscribe needs to 'Accept' json or protobuf. Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response); }
// This test verifies that only authorized principals // can access the '/flags' endpoint. TYPED_TEST(SlaveAuthorizerTest, AuthorizeFlagsEndpoint) { const string endpoint = "flags"; // Setup ACLs so that only the default principal // can access the '/flags' endpoint. ACLs acls; acls.set_permissive(false); mesos::ACL::GetEndpoint* acl = acls.add_get_endpoints(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_paths()->add_values("/" + endpoint); // Create an `Authorizer` with the ACLs. Try<Authorizer*> create = TypeParam::create(parameterize(acls)); ASSERT_SOME(create); Owned<Authorizer> authorizer(create.get()); StandaloneMasterDetector detector; Try<Owned<cluster::Slave>> agent = this->StartSlave(&detector, authorizer.get()); ASSERT_SOME(agent); Future<Response> response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body; response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL_2)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response) << response.get().body; }
TEST_F(SchedulerHttpApiTest, GetRequest) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Future<Response> response = process::http::get( master.get()->pid, "api/v1/scheduler", None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_READY(response); AWAIT_EXPECT_RESPONSE_STATUS_EQ(MethodNotAllowed({"POST"}).status, response); }
// This test verifies if the role is invalid in scheduler's framework message, // the event is error on the stream in response to a Subscribe call request. TEST_P(SchedulerHttpApiTest, RejectFrameworkWithInvalidRole) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); v1::FrameworkInfo framework = v1::DEFAULT_FRAMEWORK_INFO; // Set invalid role. framework.set_role("/test/test1"); subscribe->mutable_framework_info()->CopyFrom(framework); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check event type is error. ASSERT_EQ(Event::ERROR, event.get().get().type()); }
// TODO(anand): Add additional tests for validation. TEST_F(SchedulerHttpApiTest, NoContentType) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Expect a BadRequest when 'Content-Type' is omitted. // // TODO(anand): Send a valid call here to ensure that // the BadRequest is only due to the missing header. Future<Response> response = process::http::post( master.get()->pid, "api/v1/scheduler", createBasicAuthHeaders(DEFAULT_CREDENTIAL), None()); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); }
// Tests that requests for an agent endpoint // always succeed if the authorizer is absent. TEST_P(SlaveEndpointTest, NoAuthorizer) { const string endpoint = GetParam(); StandaloneMasterDetector detector; Try<Owned<cluster::Slave>> agent = StartSlave(&detector, CreateSlaveFlags()); ASSERT_SOME(agent); Future<Response> response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body; }
// This test sends a valid JSON blob that cannot be deserialized // into a valid protobuf resulting in a BadRequest. TEST_F(SchedulerHttpApiTest, ValidJsonButInvalidProtobuf) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); JSON::Object object; object.values["string"] = "valid_json"; process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = APPLICATION_JSON; Future<Response> response = process::http::post( master.get()->pid, "api/v1/scheduler", headers, stringify(object), APPLICATION_JSON); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); }
// This test sends a malformed body that cannot be deserialized // into a valid protobuf resulting in a BadRequest. TEST_P(SchedulerHttpApiTest, MalformedContent) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); const string body = "MALFORMED_CONTENT"; const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Future<Response> response = process::http::post( master.get()->pid, "api/v1/scheduler", headers, body, contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); }
// Testing route with deprecated (but still good) ACLs. // This ACL/test will be removed at the end of the deprecation cycle on 0.27. TEST_F(TeardownTest, GoodDeprecatedACLs) { // Setup ACLs so that the default principal can teardown the // framework. ACLs acls; mesos::ACL::ShutdownFramework* acl = acls.add_shutdown_frameworks(); acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal()); acl->mutable_framework_principals()->add_values( DEFAULT_CREDENTIAL.principal()); master::Flags flags = CreateMasterFlags(); flags.acls = acls; Try<Owned<cluster::Master>> master = StartMaster(flags); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); ASSERT_EQ(DRIVER_RUNNING, driver.start()); AWAIT_READY(frameworkId); Future<Response> response = process::http::post( master.get()->pid, "teardown", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "frameworkId=" + frameworkId.get().value()); AWAIT_READY(response); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); driver.stop(); driver.join(); }
// Tests that an agent endpoint handler forms // correct queries against the authorizer. TEST_P(SlaveEndpointTest, AuthorizedRequest) { const string endpoint = GetParam(); StandaloneMasterDetector detector; MockAuthorizer mockAuthorizer; Try<Owned<cluster::Slave>> agent = StartSlave(&detector, &mockAuthorizer); ASSERT_SOME(agent); Future<authorization::Request> request; EXPECT_CALL(mockAuthorizer, authorized(_)) .WillOnce(DoAll(FutureArg<0>(&request), Return(true))); Future<Response> response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_READY(request); const string principal = DEFAULT_CREDENTIAL.principal(); EXPECT_EQ(principal, request.get().subject().value()); // TODO(bbannier): Once agent endpoint handlers use more than just // `GET_ENDPOINT_WITH_PATH` we should factor out the request method // and expected authorization action and parameterize // `SlaveEndpointTest` on that as well in addition to the endpoint. EXPECT_EQ(authorization::GET_ENDPOINT_WITH_PATH, request.get().action()); EXPECT_EQ("/" + endpoint, request.get().object().value()); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response) << response.get().body; }
// Tests that unauthorized requests for an agent endpoint are properly rejected. TEST_P(SlaveEndpointTest, UnauthorizedRequest) { const string endpoint = GetParam(); StandaloneMasterDetector detector; MockAuthorizer mockAuthorizer; Try<Owned<cluster::Slave>> agent = StartSlave(&detector, &mockAuthorizer); ASSERT_SOME(agent); EXPECT_CALL(mockAuthorizer, authorized(_)) .WillOnce(Return(false)); Future<Response> response = http::get( agent.get()->pid, endpoint, None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(Forbidden().status, response) << response.get().body; }
// This test verifies if the scheduler can subscribe on retrying, // e.g. after a ZK blip. TEST_P(SchedulerHttpApiTest, SubscribedOnRetry) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); v1::FrameworkID frameworkId; { Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); ASSERT_EQ(Response::PIPE, response.get().type); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); frameworkId = event.get().get().subscribed().framework_id(); // Check event type is subscribed and the framework id is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_NE("", event.get().get().subscribed().framework_id().value()); } { call.mutable_framework_id()->CopyFrom(frameworkId); subscribe->mutable_framework_info()->mutable_id()->CopyFrom(frameworkId); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); // Check if we were successfully able to subscribe after the blip. Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check event type is subscribed and the same framework id is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_EQ(frameworkId, event.get().get().subscribed().framework_id()); // Make sure it receives a heartbeat. event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::HEARTBEAT, event.get().get().type()); } }
// This test verifies if the scheduler is able to receive a Subscribed // event and heartbeat events on the stream in response to a Subscribe // call request. TEST_P(SchedulerHttpApiTest, Subscribe) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); ASSERT_TRUE(response.get().headers.contains("Mesos-Stream-Id")); EXPECT_NE("", response.get().headers.at("Mesos-Stream-Id")); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check event type is subscribed and the framework id is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_NE("", event.get().get().subscribed().framework_id().value()); // Make sure it receives a heartbeat. event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::HEARTBEAT, event.get().get().type()); // Advance the clock to receive another heartbeat. Clock::pause(); Clock::advance(DEFAULT_HEARTBEAT_INTERVAL); event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::HEARTBEAT, event.get().get().type()); }
// This test verifies that the scheduler will receive a `BadRequest` response // when it tries to acknowledge a status update with a malformed UUID. TEST_P(SchedulerHttpApiTest, MalformedUUID) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; v1::FrameworkID frameworkId; string streamId; // Subscribe once to get a valid stream ID. { Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); ASSERT_EQ(Response::PIPE, response.get().type); ASSERT_TRUE(response.get().headers.contains("Mesos-Stream-Id")); streamId = response.get().headers.at("Mesos-Stream-Id"); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check that the event type is subscribed and the framework ID is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); frameworkId = event.get().get().subscribed().framework_id(); EXPECT_NE("", frameworkId.value()); } // Make an acknowledge call with a malformed UUID. This should result in a // `BadResponse`. { headers["Mesos-Stream-Id"] = streamId; Call call; call.set_type(Call::ACKNOWLEDGE); // Set the framework ID in the subscribe call. call.mutable_framework_id()->CopyFrom(frameworkId); Call::Acknowledge* acknowledge = call.mutable_acknowledge(); acknowledge->mutable_task_id()->set_value("task-id"); acknowledge->mutable_agent_id()->set_value("agent-id"); // Set a malformed uuid. acknowledge->set_uuid("bad-uuid"); Future<Response> response = process::http::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); AWAIT_EXPECT_RESPONSE_BODY_EQ( "Failed to validate scheduler::Call: Not a valid UUID", response); } }
// This test verifies that the scheduler will receive a `BadRequest` response // when a teardown call is made with an incorrect stream ID header. TEST_P(SchedulerHttpApiTest, TeardownWrongStreamId) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; v1::FrameworkID frameworkId; string streamId; // Subscribe once to get a valid stream ID. { Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); ASSERT_TRUE(response.get().headers.contains("Mesos-Stream-Id")); streamId = response.get().headers.at("Mesos-Stream-Id"); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check that the event type is subscribed and the framework ID is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_NE("", event.get().get().subscribed().framework_id().value()); frameworkId = event.get().get().subscribed().framework_id(); } // Subscribe again to invalidate the first stream ID and acquire another one. { Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(v1::DEFAULT_FRAMEWORK_INFO); // Set the framework ID in the subscribe call. call.mutable_framework_id()->CopyFrom(frameworkId); subscribe->mutable_framework_info()->mutable_id()->CopyFrom(frameworkId); Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); ASSERT_TRUE(response.get().headers.contains("Mesos-Stream-Id")); // Make sure that the new stream ID is different. ASSERT_NE(streamId, response.get().headers.at("Mesos-Stream-Id")); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_NE("", event.get().get().subscribed().framework_id().value()); } { Call call; call.set_type(Call::TEARDOWN); call.mutable_framework_id()->CopyFrom(frameworkId); // Send the first (now incorrect) stream ID with the teardown call. headers["Mesos-Stream-Id"] = streamId; Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); } }
TEST_F(HealthTest, ObserveEndpoint) { Try<PID<Master>> master = StartMaster(); ASSERT_SOME(master); // Empty get to the observe endpoint. Future<Response> response = process::http::get( master.get(), "observe", None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'"); // Empty post to the observe endpoint. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL)); VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'"); // Query string is ignored. response = process::http::post( master.get(), "observe?monitor=foo", createBasicAuthHeaders(DEFAULT_CREDENTIAL)); VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'"); // Malformed value causes error. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=foo%"); VALIDATE_BAD_RESPONSE( response, "Unable to decode query string: Malformed % escape in 'foo%': '%'"); // Empty value causes error. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor="); VALIDATE_BAD_RESPONSE(response, "Empty string for 'monitor'"); // Missing hosts. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a"); VALIDATE_BAD_RESPONSE(response, "Missing value for 'hosts'"); // Missing level. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b"); VALIDATE_BAD_RESPONSE(response, "Missing value for 'level'"); // Good request is successful. JsonResponse expected; expected.monitor = "a"; expected.hosts.push_back("b"); expected.isHealthy = true; response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b&level=ok"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); // ok is case-insensitive. response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b&level=Ok"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b&level=oK"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b&level=OK"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); // level != OK is unhealthy. expected.isHealthy = false; response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b&level=true"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); // Comma-separated hosts are parsed into an array. expected.hosts.push_back("e"); response = process::http::post( master.get(), "observe", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "monitor=a&hosts=b,e&level=true"); VALIDATE_GOOD_RESPONSE(response, stringify(expected)); Shutdown(); }
// This test verifies that we are able to downgrade from a HTTP based // framework to PID. TEST_P(SchedulerHttpApiTest, UpdateHttpToPidScheduler) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); v1::FrameworkInfo frameworkInfo = v1::DEFAULT_FRAMEWORK_INFO; Call call; call.set_type(Call::SUBSCRIBE); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(frameworkInfo); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check event type is subscribed and the framework id is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); frameworkInfo.mutable_id()-> CopyFrom(event.get().get().subscribed().framework_id()); // Make sure it receives a heartbeat. event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::HEARTBEAT, event.get().get().type()); MockScheduler sched; MesosSchedulerDriver driver( &sched, devolve(frameworkInfo), master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); driver.start(); AWAIT_READY(frameworkId); ASSERT_EQ(evolve(frameworkId.get()), frameworkInfo.id()); driver.stop(); driver.join(); }
// This test verifies if we are able to upgrade from a PID based // scheduler to HTTP scheduler. TEST_P(SchedulerHttpApiTest, UpdatePidToHttpScheduler) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); v1::FrameworkInfo frameworkInfo = v1::DEFAULT_FRAMEWORK_INFO; frameworkInfo.set_failover_timeout(Weeks(2).secs()); MockScheduler sched; StandaloneMasterDetector detector(master.get()->pid); TestingMesosSchedulerDriver driver(&sched, &detector, devolve(frameworkInfo)); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); // Check that driver is notified with an error when the http // framework is connected. Future<FrameworkErrorMessage> errorMessage = FUTURE_PROTOBUF(FrameworkErrorMessage(), _, _); EXPECT_CALL(sched, error(_, _)); driver.start(); AWAIT_READY(frameworkId); EXPECT_NE("", frameworkId.get().value()); // Now try to subscribe as an HTTP framework. Call call; call.set_type(Call::SUBSCRIBE); call.mutable_framework_id()->CopyFrom(evolve(frameworkId.get())); Call::Subscribe* subscribe = call.mutable_subscribe(); subscribe->mutable_framework_info()->CopyFrom(frameworkInfo); subscribe->mutable_framework_info()->mutable_id()-> CopyFrom(evolve(frameworkId.get())); // Retrieve the parameter passed as content type to this test. const string contentType = GetParam(); process::http::Headers headers = createBasicAuthHeaders(DEFAULT_CREDENTIAL); headers["Accept"] = contentType; Future<Response> response = process::http::streaming::post( master.get()->pid, "api/v1/scheduler", headers, serialize(call, contentType), contentType); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ("chunked", "Transfer-Encoding", response); ASSERT_EQ(Response::PIPE, response.get().type); Option<Pipe::Reader> reader = response.get().reader; ASSERT_SOME(reader); auto deserializer = lambda::bind( &SchedulerHttpApiTest::deserialize, this, contentType, lambda::_1); Reader<Event> responseDecoder(Decoder<Event>(deserializer), reader.get()); Future<Result<Event>> event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); // Check event type is subscribed and the framework id is set. ASSERT_EQ(Event::SUBSCRIBED, event.get().get().type()); EXPECT_EQ(evolve(frameworkId.get()), event.get().get().subscribed().framework_id()); // Make sure it receives a heartbeat. event = responseDecoder.read(); AWAIT_READY(event); ASSERT_SOME(event.get()); ASSERT_EQ(Event::HEARTBEAT, event.get().get().type()); driver.stop(); driver.join(); }
// Testing route with authorization header and good credentials. TEST_F(TeardownTest, Success) { Try<Owned<cluster::Master>> master = StartMaster(); ASSERT_SOME(master); MockScheduler sched; MesosSchedulerDriver driver( &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL); Future<FrameworkID> frameworkId; EXPECT_CALL(sched, registered(&driver, _, _)) .WillOnce(FutureArg<1>(&frameworkId)); ASSERT_EQ(DRIVER_RUNNING, driver.start()); AWAIT_READY(frameworkId); { Future<Response> response = process::http::post( master.get()->pid, "teardown", createBasicAuthHeaders(DEFAULT_CREDENTIAL), "frameworkId=" + frameworkId.get().value()); AWAIT_READY(response); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); } // Check that the framework that was shutdown appears in the // "completed_frameworks" list in the master's "/state" endpoint. { Future<Response> response = process::http::get( master.get()->pid, "state", None(), createBasicAuthHeaders(DEFAULT_CREDENTIAL)); AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response); Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body); ASSERT_SOME(parse); JSON::Array frameworks = parse->values["frameworks"].as<JSON::Array>(); EXPECT_TRUE(frameworks.values.empty()); JSON::Array completedFrameworks = parse->values["completed_frameworks"].as<JSON::Array>(); ASSERT_EQ(1u, completedFrameworks.values.size()); JSON::Object completedFramework = completedFrameworks.values.front().as<JSON::Object>(); JSON::String completedFrameworkId = completedFramework.values["id"].as<JSON::String>(); EXPECT_EQ(frameworkId.get(), completedFrameworkId.value); } driver.stop(); driver.join(); }