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>

Reply via email to