Implemented 'READ_FILE' for v1 Master API. Review: https://reviews.apache.org/r/49244/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/91308287 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/91308287 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/91308287 Branch: refs/heads/master Commit: 913082877c8f6c27839d8ad15a133a06ff2dcd20 Parents: 12d15e4 Author: zhou xing <xingz...@cn.ibm.com> Authored: Fri Jul 8 21:43:04 2016 -0700 Committer: Anand Mazumdar <an...@apache.org> Committed: Fri Jul 8 21:59:31 2016 -0700 ---------------------------------------------------------------------- src/master/http.cpp | 52 +++++++++++++++++++- src/master/master.hpp | 5 ++ src/tests/api_tests.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/91308287/src/master/http.cpp ---------------------------------------------------------------------- diff --git a/src/master/http.cpp b/src/master/http.cpp index 8b7e917..42ba364 100644 --- a/src/master/http.cpp +++ b/src/master/http.cpp @@ -520,7 +520,7 @@ Future<Response> Master::Http::api( return listFiles(call, principal, acceptType); case mesos::master::Call::READ_FILE: - return NotImplemented(); + return readFile(call, principal, acceptType); case mesos::master::Call::GET_STATE: return getState(call, principal, acceptType); @@ -2755,6 +2755,56 @@ Future<Response> Master::Http::state( } +Future<Response> Master::Http::readFile( + const mesos::master::Call& call, + const Option<string>& principal, + ContentType contentType) const +{ + CHECK_EQ(mesos::master::Call::READ_FILE, call.type()); + + const size_t offset = call.read_file().offset(); + const string& path = call.read_file().path(); + + Option<size_t> length; + if (call.read_file().has_length()) { + length = call.read_file().length(); + } + + return master->files->read(offset, length, path, principal) + .then([contentType](const Try<tuple<size_t, string>, FilesError>& result) + -> Future<Response> { + if (result.isError()) { + const FilesError& error = result.error(); + + switch (error.type) { + case FilesError::Type::INVALID: + return BadRequest(error.message); + + case FilesError::Type::UNAUTHORIZED: + return Forbidden(error.message); + + case FilesError::Type::NOT_FOUND: + return NotFound(error.message); + + case FilesError::Type::UNKNOWN: + return InternalServerError(error.message); + } + + UNREACHABLE(); + } + + mesos::master::Response response; + response.set_type(mesos::master::Response::READ_FILE); + + response.mutable_read_file()->set_size(std::get<0>(result.get())); + response.mutable_read_file()->set_data(std::get<1>(result.get())); + + return OK(serialize(contentType, evolve(response)), + stringify(contentType)); + }); +} + + // This abstraction has no side-effects. It factors out computing the // mapping from 'slaves' to 'frameworks' to answer the questions 'what // frameworks are running on a given slave?' and 'what slaves are http://git-wip-us.apache.org/repos/asf/mesos/blob/91308287/src/master/master.hpp ---------------------------------------------------------------------- diff --git a/src/master/master.hpp b/src/master/master.hpp index 845f2f6..ac998b1 100644 --- a/src/master/master.hpp +++ b/src/master/master.hpp @@ -1521,6 +1521,11 @@ private: const Option<std::string>& principal, ContentType contentType) const; + process::Future<process::http::Response> readFile( + const mesos::master::Call& call, + const Option<std::string>& principal, + ContentType contentType) const; + Master* master; // NOTE: The quota specific pieces of the Operator API are factored http://git-wip-us.apache.org/repos/asf/mesos/blob/91308287/src/tests/api_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/api_tests.cpp b/src/tests/api_tests.cpp index fe26351..239ab50 100644 --- a/src/tests/api_tests.cpp +++ b/src/tests/api_tests.cpp @@ -1972,6 +1972,117 @@ TEST_P(MasterAPITest, UpdateWeights) } +// This test verifies if we can retrieve file data in the master. +TEST_P(MasterAPITest, ReadFile) +{ + Files files; + + // Now write a file. + ASSERT_SOME(os::write("file", "body")); + AWAIT_EXPECT_READY(files.attach("file", "myname")); + + Try<Owned<cluster::Master>> master = StartMaster(); + ASSERT_SOME(master); + + ContentType contentType = GetParam(); + + { + v1::master::Call v1Call; + v1Call.set_type(v1::master::Call::READ_FILE); + + v1::master::Call::ReadFile* readFile = v1Call.mutable_read_file(); + readFile->set_offset(1); + readFile->set_length(2); + readFile->set_path("myname"); + + Future<v1::master::Response> v1Response = + post(master.get()->pid, v1Call, contentType); + + AWAIT_READY(v1Response); + + ASSERT_TRUE(v1Response.get().IsInitialized()); + ASSERT_EQ(v1::master::Response::READ_FILE, v1Response.get().type()); + + ASSERT_EQ("od", v1Response.get().read_file().data()); + ASSERT_EQ(4, v1Response.get().read_file().size()); + } + + // Read the file with `offset >= size`. This should return the size of file + // and empty data. + { + v1::master::Call v1Call; + v1Call.set_type(v1::master::Call::READ_FILE); + + v1::master::Call::ReadFile* readFile = v1Call.mutable_read_file(); + readFile->set_offset(5); + readFile->set_length(2); + readFile->set_path("myname"); + + Future<v1::master::Response> v1Response = + post(master.get()->pid, v1Call, contentType); + + AWAIT_READY(v1Response); + + ASSERT_TRUE(v1Response.get().IsInitialized()); + ASSERT_EQ(v1::master::Response::READ_FILE, v1Response.get().type()); + + ASSERT_EQ("", v1Response.get().read_file().data()); + ASSERT_EQ(4, v1Response.get().read_file().size()); + } + + // Read the file without length being set and `offset=0`. This should read + // the entire file. + { + v1::master::Call v1Call; + v1Call.set_type(v1::master::Call::READ_FILE); + + v1::master::Call::ReadFile* readFile = v1Call.mutable_read_file(); + readFile->set_offset(0); + readFile->set_path("myname"); + + Future<v1::master::Response> v1Response = + post(master.get()->pid, v1Call, contentType); + + AWAIT_READY(v1Response); + + ASSERT_TRUE(v1Response.get().IsInitialized()); + ASSERT_EQ(v1::master::Response::READ_FILE, v1Response.get().type()); + + ASSERT_EQ("body", v1Response.get().read_file().data()); + ASSERT_EQ(4, v1Response.get().read_file().size()); + } +} + + +// This test verifies that the client will receive a `NotFound` response when +// it tries to make a `READ_FILE` call with an invalid path. +TEST_P(MasterAPITest, ReadFileInvalidPath) +{ + Try<Owned<cluster::Master>> master = StartMaster(); + ASSERT_SOME(master); + + // Read an invalid file. + v1::master::Call v1Call; + v1Call.set_type(v1::master::Call::READ_FILE); + + v1::master::Call::ReadFile* readFile = v1Call.mutable_read_file(); + readFile->set_offset(1); + readFile->set_length(2); + readFile->set_path("invalid_file"); + + ContentType contentType = GetParam(); + + Future<Response> response = process::http::post( + master.get()->pid, + "api/v1", + createBasicAuthHeaders(DEFAULT_CREDENTIAL), + serialize(contentType, v1Call), + stringify(contentType)); + + AWAIT_EXPECT_RESPONSE_STATUS_EQ(NotFound().status, response); +} + + class AgentAPITest : public MesosTest, public WithParamInterface<ContentType>