You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by gr...@apache.org on 2017/07/01 00:33:34 UTC

mesos git commit: Added filtering to the '/tasks' endpoint.

Repository: mesos
Updated Branches:
  refs/heads/master cb601b225 -> 0d277bb64


Added filtering to the '/tasks' endpoint.

Added filtering to the '/tasks' endpoint.

Review: https://reviews.apache.org/r/60107/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/0d277bb6
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/0d277bb6
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/0d277bb6

Branch: refs/heads/master
Commit: 0d277bb64fa5a4d0b4f741daedf64095beab4773
Parents: cb601b2
Author: Quinn Leng <qu...@gmail.com>
Authored: Fri Jun 30 16:58:34 2017 -0700
Committer: Greg Mann <gr...@gmail.com>
Committed: Fri Jun 30 17:32:52 2017 -0700

----------------------------------------------------------------------
 src/common/http.cpp        | 126 ++++++++++++++++++++++++++
 src/common/http.hpp        |  85 ++++++++++++++++++
 src/master/http.cpp        | 191 ++++++++++++++++++++++------------------
 src/tests/master_tests.cpp | 174 +++++++++++++++++++++++++++---------
 4 files changed, 449 insertions(+), 127 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/0d277bb6/src/common/http.cpp
----------------------------------------------------------------------
diff --git a/src/common/http.cpp b/src/common/http.cpp
index 2f7718c..fdb591e 100644
--- a/src/common/http.cpp
+++ b/src/common/http.cpp
@@ -57,6 +57,8 @@ using std::set;
 using std::string;
 using std::vector;
 
+using process::Future;
+using process::Owned;
 using process::Failure;
 using process::Owned;
 
@@ -1123,4 +1125,128 @@ void logRequest(const process::http::Request& request)
                 : "");
 }
 
+
+Future<Owned<AuthorizeFrameworkInfoAcceptor>>
+  AuthorizeFrameworkInfoAcceptor::create(
+      const Option<Principal>& principal,
+      const Option<Authorizer*>& authorizer)
+{
+    if (authorizer.isNone()) {
+      return Owned<AuthorizeFrameworkInfoAcceptor>(
+          new AuthorizeFrameworkInfoAcceptor(Owned<ObjectApprover>(
+              new AcceptingObjectApprover())));
+    }
+
+    const Option<authorization::Subject> subject =
+      authorization::createSubject(principal);
+
+    return authorizer.get()->getObjectApprover(
+        subject,
+        authorization::VIEW_FRAMEWORK)
+      .then([=](const Owned<ObjectApprover>& approver) {
+        return Owned<AuthorizeFrameworkInfoAcceptor>(
+            new AuthorizeFrameworkInfoAcceptor(approver));
+      });
+}
+
+
+Future<Owned<AuthorizeTaskAcceptor>> AuthorizeTaskAcceptor::create(
+    const Option<Principal>& principal,
+    const Option<Authorizer*>& authorizer)
+{
+    if (authorizer.isNone()) {
+      return Owned<AuthorizeTaskAcceptor>(
+          new AuthorizeTaskAcceptor(Owned<ObjectApprover>(
+              new AcceptingObjectApprover())));
+    }
+
+    const Option<authorization::Subject> subject =
+      authorization::createSubject(principal);
+
+    return authorizer.get()->getObjectApprover(
+        subject,
+        authorization::VIEW_TASK)
+      .then([=](const Owned<ObjectApprover>& approver) {
+        return Owned<AuthorizeTaskAcceptor>(
+            new AuthorizeTaskAcceptor(approver));
+      });
+}
+
+
+FrameworkIDAcceptor::FrameworkIDAcceptor(
+    const Option<std::string>& _frameworkId)
+{
+  if (_frameworkId.isSome()) {
+    FrameworkID frameworkId_;
+    frameworkId_.set_value(_frameworkId.get());
+    frameworkId = frameworkId_;
+  }
+}
+
+
+TaskIDAcceptor::TaskIDAcceptor(const Option<std::string>& _taskId)
+{
+  if (_taskId.isSome()) {
+    TaskID taskId_;
+    taskId_.set_value(_taskId.get());
+    taskId = taskId_;
+  }
+}
+
+
+bool AuthorizeFrameworkInfoAcceptor::accept(const FrameworkInfo& frameworkInfo)
+{
+  ObjectApprover::Object object;
+  object.framework_info = &frameworkInfo;
+
+  Try<bool> approved = objectApprover->approved(object);
+  if (approved.isError()) {
+    LOG(WARNING) << "Error during FrameworkInfo authorization: "
+                 << approved.error();
+    return false;
+  }
+
+  return approved.get();
+}
+
+
+bool AuthorizeTaskAcceptor::accept(
+    const Task& task,
+    const FrameworkInfo& frameworkInfo)
+{
+  ObjectApprover::Object object;
+  object.task = &task;
+  object.framework_info = &frameworkInfo;
+
+  Try<bool> approved = objectApprover->approved(object);
+
+  if (approved.isError()) {
+    LOG(WARNING) << "Error during Task authorization: " << approved.error();
+    return false;
+  }
+
+  return approved.get();
+}
+
+
+bool FrameworkIDAcceptor::accept(const FrameworkID& _frameworkId)
+{
+  if (frameworkId.isSome()) {
+    return frameworkId.get() == _frameworkId;
+  }
+
+  return true;
+}
+
+
+bool TaskIDAcceptor::accept(const TaskID& _taskId)
+{
+  if (taskId.isSome()) {
+    return taskId.get() == _taskId;
+  }
+
+  return true;
+}
+
+
 }  // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/0d277bb6/src/common/http.hpp
----------------------------------------------------------------------
diff --git a/src/common/http.hpp b/src/common/http.hpp
index 93d6088..b7e4a8a 100644
--- a/src/common/http.hpp
+++ b/src/common/http.hpp
@@ -160,6 +160,91 @@ public:
 };
 
 
+/**
+ * Determines which objects will be accepted when filtering results based on
+ * authorization or other criteria.
+ */
+class ObjectAcceptor
+{
+public:
+  virtual ~ObjectAcceptor() = default;
+};
+
+
+// Parent class for authorization-based acceptors.
+class AuthorizationAcceptor : public ObjectAcceptor
+{
+protected:
+  AuthorizationAcceptor(const process::Owned<ObjectApprover>& approver)
+    : objectApprover(approver) {}
+
+  const process::Owned<ObjectApprover> objectApprover;
+};
+
+
+class AuthorizeFrameworkInfoAcceptor : public AuthorizationAcceptor
+{
+public:
+  static process::Future<process::Owned<AuthorizeFrameworkInfoAcceptor>> create(
+      const Option<process::http::authentication::Principal>& principal,
+      const Option<Authorizer*>& authorizer);
+
+  bool accept(const FrameworkInfo& frameworkInfo);
+
+protected:
+  AuthorizeFrameworkInfoAcceptor(const process::Owned<ObjectApprover>& approver)
+    : AuthorizationAcceptor(approver) {}
+};
+
+
+class AuthorizeTaskAcceptor : public AuthorizationAcceptor
+{
+public:
+  static process::Future<process::Owned<AuthorizeTaskAcceptor>> create(
+      const Option<process::http::authentication::Principal>& principal,
+      const Option<Authorizer*>& authorizer);
+
+  bool accept(
+      const Task& task,
+      const FrameworkInfo& frameworkInfo);
+
+protected:
+  AuthorizeTaskAcceptor(const process::Owned<ObjectApprover>& approver)
+    : AuthorizationAcceptor(approver) {}
+};
+
+
+/**
+ * Filtering results based on framework ID. When no framework ID is specified
+ * it will accept all inputs.
+ */
+class FrameworkIDAcceptor : public ObjectAcceptor
+{
+public:
+  FrameworkIDAcceptor(const Option<std::string>& _frameworkId);
+  bool accept(const FrameworkID& frameworkId);
+
+protected:
+  Option<FrameworkID> frameworkId;
+};
+
+
+/**
+ * Filtering results based on task ID. When no task ID is specified
+ * it will accept all inputs.
+ */
+class TaskIDAcceptor : public ObjectAcceptor
+{
+public:
+  TaskIDAcceptor(const Option<std::string>& _taskId);
+
+  bool accept(const TaskID& taskId);
+
+protected:
+  Option<TaskID> taskId;
+};
+
+
 bool approveViewFrameworkInfo(
     const process::Owned<ObjectApprover>& frameworksApprover,
     const FrameworkInfo& frameworkInfo);

http://git-wip-us.apache.org/repos/asf/mesos/blob/0d277bb6/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 4dd43fd..64b7cdd 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -3755,11 +3755,15 @@ string Master::Http::TASKS_HELP()
         "",
         "Query parameters:",
         "",
+        ">        framework_id=VALUE   Only return tasks belonging to the "
+        "framework with this ID.",
         ">        limit=VALUE          Maximum number of tasks returned "
         "(default is " + stringify(TASK_LIMIT) + ").",
         ">        offset=VALUE         Starts task list at offset.",
         ">        order=(asc|desc)     Ascending or descending sort order "
-        "(default is descending)."
+        "(default is descending).",
+        ">        task_id=VALUE        Only return tasks with this ID "
+        "(should be used together with parameter 'framework_id')."
         ""),
     AUTHENTICATION(true),
     AUTHORIZATION(
@@ -3798,106 +3802,121 @@ Future<Response> Master::Http::tasks(
   Option<string> order = request.url.query.get("order");
   string _order = order.isSome() && (order.get() == "asc") ? "asc" : "des";
 
-  // Retrieve Approvers for authorizing frameworks and tasks.
-  Future<Owned<ObjectApprover>> frameworksApprover;
-  Future<Owned<ObjectApprover>> tasksApprover;
-  if (master->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
+  Future<Owned<AuthorizeFrameworkInfoAcceptor>> authorizeFrameworkInfo =
+    AuthorizeFrameworkInfoAcceptor::create(principal, master->authorizer);
+  Future<Owned<AuthorizeTaskAcceptor>> authorizeTask =
+    AuthorizeTaskAcceptor::create(principal, master->authorizer);
+  Future<Owned<FrameworkIDAcceptor>> selectFrameworkId =
+    Owned<FrameworkIDAcceptor>(
+        new FrameworkIDAcceptor(request.url.query.get("framework_id")));
+  Future<Owned<TaskIDAcceptor>> selectTaskId =
+    Owned<TaskIDAcceptor>(new TaskIDAcceptor(request.url.query.get("task_id")));
 
-    frameworksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_FRAMEWORK);
+  return collect(
+      authorizeFrameworkInfo,
+      authorizeTask,
+      selectFrameworkId,
+      selectTaskId)
+    .then(defer(
+        master->self(),
+        [=](const tuple<Owned<AuthorizeFrameworkInfoAcceptor>,
+                        Owned<AuthorizeTaskAcceptor>,
+                        Owned<FrameworkIDAcceptor>,
+                        Owned<TaskIDAcceptor>>& acceptors)-> Future<Response> {
+          Owned<AuthorizeFrameworkInfoAcceptor> authorizeFrameworkInfo;
+          Owned<AuthorizeTaskAcceptor> authorizeTask;
+          Owned<FrameworkIDAcceptor> selectFrameworkId;
+          Owned<TaskIDAcceptor> selectTaskId;
+          tie(authorizeFrameworkInfo,
+              authorizeTask,
+              selectFrameworkId,
+              selectTaskId) = acceptors;
+
+          // Construct framework list with both active and completed frameworks.
+          vector<const Framework*> frameworks;
+          foreachvalue (Framework* framework, master->frameworks.registered) {
+            // Skip unauthorized frameworks or frameworks without matching
+            // framework ID.
+            if (!selectFrameworkId->accept(framework->id()) ||
+                !authorizeFrameworkInfo->accept(framework->info)) {
+              continue;
+            }
 
-    tasksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_TASK);
-  } else {
-    frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+            frameworks.push_back(framework);
+          }
 
-  return collect(frameworksApprover, tasksApprover)
-    .then(defer(master->self(),
-      [=](const tuple<Owned<ObjectApprover>,
-                      Owned<ObjectApprover>>& approvers)
-        -> Future<Response> {
-      // Get approver from tuple.
-      Owned<ObjectApprover> frameworksApprover;
-      Owned<ObjectApprover> tasksApprover;
-      tie(frameworksApprover, tasksApprover) = approvers;
+          foreachvalue (const Owned<Framework>& framework,
+                        master->frameworks.completed) {
+            // Skip unauthorized frameworks or frameworks without matching
+            // framework ID.
+            if (!selectFrameworkId->accept(framework->id()) ||
+                !authorizeFrameworkInfo->accept(framework->info)) {
+             continue;
+            }
 
-      // Construct framework list with both active and completed frameworks.
-      vector<const Framework*> frameworks;
-      foreachvalue (Framework* framework, master->frameworks.registered) {
-        // Skip unauthorized frameworks.
-        if (!approveViewFrameworkInfo(frameworksApprover, framework->info)) {
-          continue;
-        }
+            frameworks.push_back(framework.get());
+          }
 
-        frameworks.push_back(framework);
-      }
+          // Construct task list with both running,
+          // completed and unreachable tasks.
+          vector<const Task*> tasks;
+          foreach (const Framework* framework, frameworks) {
+            foreachvalue (Task* task, framework->tasks) {
+              CHECK_NOTNULL(task);
+              // Skip unauthorized tasks or tasks without matching task ID.
+              if (!selectTaskId->accept(task->task_id()) ||
+                  !authorizeTask->accept(*task, framework->info)) {
+                continue;
+              }
 
-      foreachvalue (const Owned<Framework>& framework,
-                    master->frameworks.completed) {
-        // Skip unauthorized frameworks.
-        if (!approveViewFrameworkInfo(frameworksApprover, framework->info)) {
-          continue;
-        }
+              tasks.push_back(task);
+            }
 
-        frameworks.push_back(framework.get());
-      }
+            foreachvalue (
+                const Owned<Task>& task,
+                framework->unreachableTasks) {
+              // Skip unauthorized tasks or tasks without matching task ID.
+              if (!selectTaskId->accept(task.get()->task_id()) ||
+                  !authorizeTask->accept(*task.get(), framework->info)) {
+                continue;
+              }
 
-      // Construct task list with both running and finished tasks.
-      vector<const Task*> tasks;
-      foreach (const Framework* framework, frameworks) {
-        foreachvalue (Task* task, framework->tasks) {
-          CHECK_NOTNULL(task);
-          // Skip unauthorized tasks.
-          if (!approveViewTask(tasksApprover, *task, framework->info)) {
-            continue;
-          }
+              tasks.push_back(task.get());
+            }
 
-          tasks.push_back(task);
-        }
+            foreach (const Owned<Task>& task, framework->completedTasks) {
+              // Skip unauthorized tasks or tasks without matching task ID.
+              if (!selectTaskId->accept(task.get()->task_id()) ||
+                  !authorizeTask->accept(*task.get(), framework->info)) {
+                continue;
+              }
 
-        foreachvalue (const Owned<Task>& task, framework->unreachableTasks) {
-          // Skip unauthorized tasks.
-          if (!approveViewTask(tasksApprover, *task.get(), framework->info)) {
-            continue;
+              tasks.push_back(task.get());
+            }
           }
 
-          tasks.push_back(task.get());
-        }
-
-        foreach (const Owned<Task>& task, framework->completedTasks) {
-          // Skip unauthorized tasks.
-          if (!approveViewTask(tasksApprover, *task.get(), framework->info)) {
-            continue;
+          // Sort tasks by task status timestamp. Default order is descending.
+          // The earliest timestamp is chosen for comparison when
+          // multiple are present.
+          if (_order == "asc") {
+            sort(tasks.begin(), tasks.end(), TaskComparator::ascending);
+          } else {
+            sort(tasks.begin(), tasks.end(), TaskComparator::descending);
           }
 
-          tasks.push_back(task.get());
-        }
-      }
-
-      // Sort tasks by task status timestamp. Default order is descending.
-      // The earliest timestamp is chosen for comparison when
-      // multiple are present.
-      if (_order == "asc") {
-        sort(tasks.begin(), tasks.end(), TaskComparator::ascending);
-      } else {
-        sort(tasks.begin(), tasks.end(), TaskComparator::descending);
-      }
-
-      auto tasksWriter = [&tasks, limit, offset](JSON::ObjectWriter* writer) {
-        writer->field("tasks",
-                      [&tasks, limit, offset](JSON::ArrayWriter* writer) {
-          // Collect 'limit' number of tasks starting from 'offset'.
-          size_t end = std::min(offset + limit, tasks.size());
-          for (size_t i = offset; i < end; i++) {
-            writer->element(*tasks[i]);
-          }
-        });
-      };
+          auto tasksWriter =
+            [&tasks, limit, offset](JSON::ObjectWriter* writer) {
+            writer->field("tasks",
+                          [&tasks, limit, offset](JSON::ArrayWriter* writer) {
+              // Collect 'limit' number of tasks starting from 'offset'.
+              size_t end = std::min(offset + limit, tasks.size());
+              for (size_t i = offset; i < end; i++) {
+                writer->element(*tasks[i]);
+              }
+            });
+          };
 
-      return OK(jsonify(tasksWriter), request.url.query.get("jsonp"));
+          return OK(jsonify(tasksWriter), request.url.query.get("jsonp"));
   }));
 }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/0d277bb6/src/tests/master_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_tests.cpp b/src/tests/master_tests.cpp
index cfb799f..d21194f 100644
--- a/src/tests/master_tests.cpp
+++ b/src/tests/master_tests.cpp
@@ -3420,7 +3420,8 @@ TEST_F(MasterTest, UnregisteredFrameworksAfterTearDown)
 // This tests /tasks endpoint to return correct task information.
 TEST_F(MasterTest, TasksEndpoint)
 {
-  Try<Owned<cluster::Master>> master = StartMaster();
+  master::Flags masterFlags = CreateMasterFlags();
+  Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
   ASSERT_SOME(master);
 
   MockExecutor exec(DEFAULT_EXECUTOR_ID);
@@ -3434,67 +3435,158 @@ TEST_F(MasterTest, TasksEndpoint)
   MesosSchedulerDriver driver(
       &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL);
 
-  EXPECT_CALL(sched, registered(&driver, _, _));
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
 
-  Future<vector<Offer>> offers;
+  process::Queue<Offer> offers;
   EXPECT_CALL(sched, resourceOffers(&driver, _))
-    .WillOnce(FutureArg<1>(&offers))
-    .WillRepeatedly(Return()); // Ignore subsequent offers.
+    .WillRepeatedly(EnqueueOffers(&offers));
 
   driver.start();
 
-  AWAIT_READY(offers);
-  ASSERT_NE(0u, offers->size());
+  Future<Offer> offer = offers.get();
+  AWAIT_READY(offer);
 
-  TaskInfo task;
-  task.set_name("test");
-  task.mutable_task_id()->set_value("1");
-  task.mutable_slave_id()->MergeFrom(offers.get()[0].slave_id());
-  task.mutable_resources()->MergeFrom(offers.get()[0].resources());
-  task.mutable_executor()->MergeFrom(DEFAULT_EXECUTOR_INFO);
+  // Launch two tasks.
+  TaskInfo task1;
+  task1.set_name("test1");
+  task1.mutable_task_id()->set_value("1");
+  task1.mutable_slave_id()->MergeFrom(offer->slave_id());
+  task1.mutable_resources()->MergeFrom(
+      Resources::parse("cpus:0.1;mem:12").get());
+  task1.mutable_executor()->MergeFrom(DEFAULT_EXECUTOR_INFO);
+
+  TaskInfo task2;
+  task2.set_name("test2");
+  task2.mutable_task_id()->set_value("2");
+  task2.mutable_slave_id()->MergeFrom(offer->slave_id());
+  task2.mutable_resources()->MergeFrom(
+      Resources::parse("cpus:0.1;mem:12").get());
+  task2.mutable_executor()->MergeFrom(DEFAULT_EXECUTOR_INFO);
+
+  vector<TaskInfo> tasks;
+  tasks.push_back(task1);
+  tasks.push_back(task2);
 
   EXPECT_CALL(exec, registered(_, _, _, _));
 
   EXPECT_CALL(exec, launchTask(_, _))
+    .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
     .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING));
 
-  Future<TaskStatus> status;
+  Future<TaskStatus> status1, status2;
   EXPECT_CALL(sched, statusUpdate(&driver, _))
-    .WillOnce(FutureArg<1>(&status));
+    .WillOnce(FutureArg<1>(&status1))
+    .WillOnce(FutureArg<1>(&status2));
 
-  driver.launchTasks(offers.get()[0].id(), {task});
+  driver.launchTasks(offer->id(), tasks);
 
-  AWAIT_READY(status);
-  EXPECT_EQ(TASK_RUNNING, status->state());
-  EXPECT_TRUE(status->has_executor_id());
-  EXPECT_EQ(exec.id, status->executor_id());
+  AWAIT_READY(status1);
+  EXPECT_EQ(TASK_RUNNING, status1->state());
+  EXPECT_TRUE(status1->has_executor_id());
+  EXPECT_EQ(exec.id, status1->executor_id());
 
-  Future<Response> response = process::http::get(
-      master.get()->pid,
-      "tasks",
-      None(),
-      createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+  AWAIT_READY(status2);
+  EXPECT_EQ(TASK_RUNNING, status2->state());
+  EXPECT_TRUE(status2->has_executor_id());
+  EXPECT_EQ(exec.id, status2->executor_id());
 
-  AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
-  AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
+  // Testing the '/master/tasks' endpoint without parameters,
+  // which returns information about all tasks.
+  {
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "tasks",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
 
-  Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
-  ASSERT_SOME(value);
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+    AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
 
-  Try<JSON::Value> expected = JSON::parse(
-      "{"
-        "\"tasks\":"
-          "[{"
-              "\"executor_id\":\"default\","
-              "\"id\":\"1\","
-              "\"name\":\"test\","
-              "\"state\":\"TASK_RUNNING\""
-          "}]"
-      "}");
+    Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
+    ASSERT_SOME(value);
+
+    // Two possible orderings of the result.
+    Try<JSON::Value> expected1 = JSON::parse(
+        "{"
+          "\"tasks\":"
+            "[{"
+                "\"executor_id\":\"default\","
+                "\"framework_id\":\"" + frameworkId->value() + "\","
+                "\"id\":\"1\","
+                "\"name\":\"test1\","
+                "\"state\":\"TASK_RUNNING\""
+              "},{"
+                "\"executor_id\":\"default\","
+                "\"framework_id\":\"" + frameworkId->value() + "\","
+                "\"id\":\"2\","
+                "\"name\":\"test2\","
+                "\"state\":\"TASK_RUNNING\""
+            "}]"
+        "}");
+
+    Try<JSON::Value> expected2 = JSON::parse(
+        "{"
+          "\"tasks\":"
+            "[{"
+                "\"executor_id\":\"default\","
+                "\"framework_id\":\"" + frameworkId->value() + "\","
+                "\"id\":\"2\","
+                "\"name\":\"test2\","
+                "\"state\":\"TASK_RUNNING\""
+              "},{"
+                "\"executor_id\":\"default\","
+                "\"framework_id\":\"" + frameworkId->value() + "\","
+                "\"id\":\"1\","
+                "\"name\":\"test1\","
+                "\"state\":\"TASK_RUNNING\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected1);
+    ASSERT_SOME(expected2);
+
+    EXPECT_TRUE(
+        value->contains(expected1.get()) ||
+        value->contains(expected2.get()));
+  }
 
-  ASSERT_SOME(expected);
+  // Testing the query for a specific task.
+  {
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "tasks?task_id=1;framework_id=" + frameworkId->value(),
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
 
-  EXPECT_TRUE(value->contains(expected.get()));
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+    AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
+
+    Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
+    ASSERT_SOME(value);
+
+    JSON::Object object = value->as<JSON::Object>();
+    Result<JSON::Array> taskArray = object.find<JSON::Array>("tasks");
+    ASSERT_SOME(taskArray);
+
+    EXPECT_TRUE(taskArray->values.size() == 1);
+
+    Try<JSON::Value> expected = JSON::parse(
+        "{"
+          "\"tasks\":"
+            "[{"
+                "\"executor_id\":\"default\","
+                "\"framework_id\":\"" + frameworkId->value() + "\","
+                "\"id\":\"1\","
+                "\"name\":\"test1\","
+                "\"state\":\"TASK_RUNNING\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected);
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
 
   EXPECT_CALL(exec, shutdown(_))
     .Times(AtMost(1));