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/19 00:19:51 UTC

[1/3] mesos git commit: Added class definition for the 'IDAcceptor'.

Repository: mesos
Updated Branches:
  refs/heads/master 91b3abcfa -> 916a5c9fd


Added class definition for the 'IDAcceptor'.

This commit contains the class definition for 'IDAcceptor', which is
used to filter IDs in the '/master/frameworks', '/master/slaves',
'/master/tasks', and '/slave/containers' endpoints.

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


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

Branch: refs/heads/master
Commit: aa244baa45d8db84e98e6dca9944a3f679da70d1
Parents: 91b3abc
Author: Quinn Leng <qu...@gmail.com>
Authored: Tue Jul 18 17:06:56 2017 -0700
Committer: Greg Mann <gr...@gmail.com>
Committed: Tue Jul 18 17:07:40 2017 -0700

----------------------------------------------------------------------
 src/common/http.cpp | 41 -----------------------------------------
 src/common/http.hpp | 42 +++++++++++++++++++++---------------------
 src/master/http.cpp | 27 +++++++++++++--------------
 3 files changed, 34 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/aa244baa/src/common/http.cpp
----------------------------------------------------------------------
diff --git a/src/common/http.cpp b/src/common/http.cpp
index 3825a13..dfd5f33 100644
--- a/src/common/http.cpp
+++ b/src/common/http.cpp
@@ -1165,45 +1165,4 @@ Future<Owned<AuthorizationAcceptor>> AuthorizationAcceptor::create(
 }
 
 
-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 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/aa244baa/src/common/http.hpp
----------------------------------------------------------------------
diff --git a/src/common/http.hpp b/src/common/http.hpp
index 4822a23..ba8dda1 100644
--- a/src/common/http.hpp
+++ b/src/common/http.hpp
@@ -195,34 +195,34 @@ protected:
 
 
 /**
- * Filtering results based on framework ID. When no framework ID is specified
- * it will accept all inputs.
+ * Used to filter results for API handlers. Provides the 'accept()' method to
+ * test whether the supplied ID is equal to a stored target ID. If no target
+ * ID is provided when the acceptor is constructed, it will accept all inputs.
  */
-class FrameworkIDAcceptor
+template <typename T>
+class IDAcceptor
 {
 public:
-  FrameworkIDAcceptor(const Option<std::string>& _frameworkId);
-
-  bool accept(const FrameworkID& frameworkId);
-
-protected:
-  Option<FrameworkID> frameworkId;
-};
-
+  IDAcceptor(const Option<std::string>& id = None())
+  {
+    if (id.isSome()) {
+      T targetId_;
+      targetId_.set_value(id.get());
+      targetId = targetId_;
+    }
+  }
 
-/**
- * Filtering results based on task ID. When no task ID is specified
- * it will accept all inputs.
- */
-class TaskIDAcceptor
-{
-public:
-  TaskIDAcceptor(const Option<std::string>& _taskId);
+  bool accept(const T& candidateId) const
+  {
+    if (targetId.isNone()) {
+      return true;
+    }
 
-  bool accept(const TaskID& taskId);
+    return candidateId.value() == targetId->value();
+  }
 
 protected:
-  Option<TaskID> taskId;
+  Option<T> targetId;
 };
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/aa244baa/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 3ddb54b..cbe6d96 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -3900,11 +3900,10 @@ Future<Response> Master::Http::tasks(
         principal,
         master->authorizer,
         authorization::VIEW_TASK);
-  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")));
+  Future<IDAcceptor<FrameworkID>> selectFrameworkId =
+    IDAcceptor<FrameworkID>(request.url.query.get("framework_id"));
+  Future<IDAcceptor<TaskID>> selectTaskId =
+    IDAcceptor<TaskID>(request.url.query.get("task_id"));
 
   return collect(
       authorizeFrameworkInfo,
@@ -3915,12 +3914,12 @@ Future<Response> Master::Http::tasks(
         master->self(),
         [=](const tuple<Owned<AuthorizationAcceptor>,
                         Owned<AuthorizationAcceptor>,
-                        Owned<FrameworkIDAcceptor>,
-                        Owned<TaskIDAcceptor>>& acceptors)-> Future<Response> {
+                        IDAcceptor<FrameworkID>,
+                        IDAcceptor<TaskID>>& acceptors)-> Future<Response> {
           Owned<AuthorizationAcceptor> authorizeFrameworkInfo;
           Owned<AuthorizationAcceptor> authorizeTask;
-          Owned<FrameworkIDAcceptor> selectFrameworkId;
-          Owned<TaskIDAcceptor> selectTaskId;
+          IDAcceptor<FrameworkID> selectFrameworkId;
+          IDAcceptor<TaskID> selectTaskId;
           tie(authorizeFrameworkInfo,
               authorizeTask,
               selectFrameworkId,
@@ -3931,7 +3930,7 @@ Future<Response> Master::Http::tasks(
           foreachvalue (Framework* framework, master->frameworks.registered) {
             // Skip unauthorized frameworks or frameworks without matching
             // framework ID.
-            if (!selectFrameworkId->accept(framework->id()) ||
+            if (!selectFrameworkId.accept(framework->id()) ||
                 !authorizeFrameworkInfo->accept(framework->info)) {
               continue;
             }
@@ -3943,7 +3942,7 @@ Future<Response> Master::Http::tasks(
                         master->frameworks.completed) {
             // Skip unauthorized frameworks or frameworks without matching
             // framework ID.
-            if (!selectFrameworkId->accept(framework->id()) ||
+            if (!selectFrameworkId.accept(framework->id()) ||
                 !authorizeFrameworkInfo->accept(framework->info)) {
              continue;
             }
@@ -3958,7 +3957,7 @@ Future<Response> Master::Http::tasks(
             foreachvalue (Task* task, framework->tasks) {
               CHECK_NOTNULL(task);
               // Skip unauthorized tasks or tasks without matching task ID.
-              if (!selectTaskId->accept(task->task_id()) ||
+              if (!selectTaskId.accept(task->task_id()) ||
                   !authorizeTask->accept(*task, framework->info)) {
                 continue;
               }
@@ -3970,7 +3969,7 @@ Future<Response> Master::Http::tasks(
                 const Owned<Task>& task,
                 framework->unreachableTasks) {
               // Skip unauthorized tasks or tasks without matching task ID.
-              if (!selectTaskId->accept(task.get()->task_id()) ||
+              if (!selectTaskId.accept(task.get()->task_id()) ||
                   !authorizeTask->accept(*task.get(), framework->info)) {
                 continue;
               }
@@ -3980,7 +3979,7 @@ Future<Response> Master::Http::tasks(
 
             foreach (const Owned<Task>& task, framework->completedTasks) {
               // Skip unauthorized tasks or tasks without matching task ID.
-              if (!selectTaskId->accept(task.get()->task_id()) ||
+              if (!selectTaskId.accept(task.get()->task_id()) ||
                   !authorizeTask->accept(*task.get(), framework->info)) {
                 continue;
               }


[2/3] mesos git commit: Added filtering to /slaves, /containers and /frameworks endpoints.

Posted by gr...@apache.org.
Added filtering to /slaves, /containers and /frameworks endpoints.

Added query parameter support for the '/slaves', '/frameworks' and
'/containers' endpoints.

This allows slaves, frameworks and containers to be queried
by ID.

If no ID is specified, all records are returned, consistent
with current behavior.

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


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

Branch: refs/heads/master
Commit: 8363449c130298b9c77560c5df583dc1226dd17c
Parents: aa244ba
Author: Quinn Leng <qu...@gmail.com>
Authored: Tue Jul 18 17:06:59 2017 -0700
Committer: Greg Mann <gr...@gmail.com>
Committed: Tue Jul 18 17:08:43 2017 -0700

----------------------------------------------------------------------
 src/master/http.cpp | 445 +++++++++++++++++++++++------------------------
 src/slave/http.cpp  |  96 +++++-----
 src/slave/http.hpp  |   3 +-
 3 files changed, 265 insertions(+), 279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/8363449c/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index cbe6d96..9df086c 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -219,11 +219,11 @@ static void json(JSON::ObjectWriter* writer, const Summary<Framework>& summary);
 // user is authorized to view them.
 struct FullFrameworkWriter {
   FullFrameworkWriter(
-      const Owned<ObjectApprover>& taskApprover,
-      const Owned<ObjectApprover>& executorApprover,
+      const Owned<AuthorizationAcceptor>& authorizeTask,
+      const Owned<AuthorizationAcceptor>& authorizeExecutorInfo,
       const Framework* framework)
-    : taskApprover_(taskApprover),
-      executorApprover_(executorApprover),
+    : authorizeTask_(authorizeTask),
+      authorizeExecutorInfo_(authorizeExecutorInfo),
       framework_(framework) {}
 
   void operator()(JSON::ObjectWriter* writer) const
@@ -268,7 +268,7 @@ struct FullFrameworkWriter {
     writer->field("tasks", [this](JSON::ArrayWriter* writer) {
       foreachvalue (const TaskInfo& taskInfo, framework_->pendingTasks) {
         // Skip unauthorized tasks.
-        if (!approveViewTaskInfo(taskApprover_, taskInfo, framework_->info)) {
+        if (!authorizeTask_->accept(taskInfo, framework_->info)) {
           continue;
         }
 
@@ -309,7 +309,7 @@ struct FullFrameworkWriter {
 
       foreachvalue (Task* task, framework_->tasks) {
         // Skip unauthorized tasks.
-        if (!approveViewTask(taskApprover_, *task, framework_->info)) {
+        if (!authorizeTask_->accept(*task, framework_->info)) {
           continue;
         }
 
@@ -320,7 +320,7 @@ struct FullFrameworkWriter {
     writer->field("unreachable_tasks", [this](JSON::ArrayWriter* writer) {
       foreachvalue (const Owned<Task>& task, framework_->unreachableTasks) {
         // Skip unauthorized tasks.
-        if (!approveViewTask(taskApprover_, *task.get(), framework_->info)) {
+        if (!authorizeTask_->accept(*task.get(), framework_->info)) {
           continue;
         }
 
@@ -331,7 +331,7 @@ struct FullFrameworkWriter {
     writer->field("completed_tasks", [this](JSON::ArrayWriter* writer) {
       foreach (const Owned<Task>& task, framework_->completedTasks) {
         // Skip unauthorized tasks.
-        if (!approveViewTask(taskApprover_, *task.get(), framework_->info)) {
+        if (!authorizeTask_->accept(*task.get(), framework_->info)) {
           continue;
         }
 
@@ -357,10 +357,7 @@ struct FullFrameworkWriter {
                            &executor,
                            &slaveId](JSON::ObjectWriter* writer) {
             // Skip unauthorized executors.
-            if (!approveViewExecutorInfo(
-                    executorApprover_,
-                    executor,
-                    framework_->info)) {
+            if (!authorizeExecutorInfo_->accept(executor, framework_->info)) {
               return;
             }
 
@@ -377,16 +374,18 @@ struct FullFrameworkWriter {
     }
   }
 
-  const Owned<ObjectApprover>& taskApprover_;
-  const Owned<ObjectApprover>& executorApprover_;
+  const Owned<AuthorizationAcceptor>& authorizeTask_;
+  const Owned<AuthorizationAcceptor>& authorizeExecutorInfo_;
   const Framework* framework_;
 };
 
 
 struct SlaveWriter
 {
-  SlaveWriter(const Slave& slave, const Owned<ObjectApprover>& roleApprover)
-    : slave_(slave), roleApprover_(roleApprover) {}
+  SlaveWriter(
+      const Slave& slave,
+      const Owned<AuthorizationAcceptor>& authorizeRole)
+    : slave_(slave), authorizeRole_(authorizeRole) {}
 
   void operator()(JSON::ObjectWriter* writer) const
   {
@@ -411,7 +410,7 @@ struct SlaveWriter
             // TODO(arojas): Consider showing unapproved resources in an
             // aggregated special field, so that all resource values add up
             // MESOS-7779.
-            if (approveViewRole(roleApprover_, role)) {
+            if (authorizeRole_->accept(role)) {
               writer->field(role, reservation);
             }
           }
@@ -424,7 +423,7 @@ struct SlaveWriter
   }
 
   const Slave& slave_;
-  const Owned<ObjectApprover>& roleApprover_;
+  const Owned<AuthorizationAcceptor>& authorizeRole_;
 };
 
 
@@ -432,14 +431,20 @@ struct SlavesWriter
 {
   SlavesWriter(
       const Master::Slaves& slaves,
-      const Owned<ObjectApprover>& roleApprover)
+      const Owned<AuthorizationAcceptor>& authorizeRole,
+      const IDAcceptor<SlaveID>& selectSlaveId)
     : slaves_(slaves),
-      roleApprover_(roleApprover) {}
+      authorizeRole_(authorizeRole),
+      selectSlaveId_(selectSlaveId) {}
 
   void operator()(JSON::ObjectWriter* writer) const
   {
     writer->field("slaves", [this](JSON::ArrayWriter* writer) {
       foreachvalue (const Slave* slave, slaves_.registered) {
+        if (!selectSlaveId_.accept(slave->id)) {
+          continue;
+        }
+
         writer->element([this, &slave](JSON::ObjectWriter* writer) {
           writeSlave(slave, writer);
         });
@@ -448,6 +453,10 @@ struct SlavesWriter
 
     writer->field("recovered_slaves", [this](JSON::ArrayWriter* writer) {
       foreachvalue (const SlaveInfo& slaveInfo, slaves_.recovered) {
+        if (!selectSlaveId_.accept(slaveInfo.id())) {
+          continue;
+        }
+
         writer->element([&slaveInfo](JSON::ObjectWriter* writer) {
           json(writer, slaveInfo);
         });
@@ -457,7 +466,7 @@ struct SlavesWriter
 
   void writeSlave(const Slave* slave, JSON::ObjectWriter* writer) const
   {
-    SlaveWriter(*slave, roleApprover_)(writer);
+    SlaveWriter(*slave, authorizeRole_)(writer);
 
     // Add the complete protobuf->JSON for all used, reserved,
     // and offered resources. The other endpoints summarize
@@ -474,7 +483,7 @@ struct SlavesWriter
           foreachpair (const string& role,
                        const Resources& resources,
                        reserved) {
-            if (approveViewRole(roleApprover_, role)) {
+            if (authorizeRole_->accept(role)) {
               writer->field(role, [&resources](JSON::ArrayWriter* writer) {
                 foreach (Resource resource, resources) {
                   convertResourceFormat(&resource, ENDPOINT);
@@ -502,9 +511,8 @@ struct SlavesWriter
         "used_resources_full",
         [&usedResources, this](JSON::ArrayWriter* writer) {
           foreach (Resource resource, usedResources) {
-            if (approveViewRole(roleApprover_, resource.role()) &&
-                approveViewRole(roleApprover_,
-                                resource.allocation_info().role())) {
+            if (authorizeRole_->accept(resource.role()) &&
+                authorizeRole_->accept(resource.allocation_info().role())) {
               convertResourceFormat(&resource, ENDPOINT);
               writer->element(JSON::Protobuf(resource));
             }
@@ -517,7 +525,7 @@ struct SlavesWriter
         "offered_resources_full",
         [&offeredResources, this](JSON::ArrayWriter* writer) {
           foreach (Resource resource, offeredResources) {
-            if (approveViewRole(roleApprover_, resource.role())) {
+            if (authorizeRole_->accept(resource.role())) {
               convertResourceFormat(&resource, ENDPOINT);
               writer->element(JSON::Protobuf(resource));
             }
@@ -525,8 +533,9 @@ struct SlavesWriter
         });
   }
 
-  const Master::Slaves &slaves_;
-  const Owned<ObjectApprover> &roleApprover_;
+  const Master::Slaves& slaves_;
+  const Owned<AuthorizationAcceptor>& authorizeRole_;
+  const IDAcceptor<SlaveID>& selectSlaveId_;
 };
 
 
@@ -1467,7 +1476,11 @@ string Master::Http::FRAMEWORKS_HELP()
         "current master is not the leader.",
         "",
         "Returns 503 SERVICE_UNAVAILABLE if the leading master cannot be",
-        "found."),
+        "found.",
+        "",
+        "Query parameters:",
+        ">        framework_id=VALUE   The ID of the framework returned "
+        "(when no framework ID specified, all frameworks will be returned)."),
     AUTHENTICATION(true),
     AUTHORIZATION(
         "This endpoint might be filtered based on the user accessing it.",
@@ -1493,87 +1506,89 @@ Future<Response> Master::Http::frameworks(
     return redirect(request);
   }
 
-  // Retrieve `ObjectApprover`s for authorizing frameworks and tasks and
-  // executors.
-  Future<Owned<ObjectApprover>> frameworksApprover;
-  Future<Owned<ObjectApprover>> tasksApprover;
-  Future<Owned<ObjectApprover>> executorsApprover;
-
-  if (master->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    frameworksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_FRAMEWORK);
-
-    tasksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_TASK);
-
-    executorsApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_EXECUTOR);
-  } else {
-    frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+  Future<Owned<AuthorizationAcceptor>> authorizeFrameworkInfo =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_FRAMEWORK);
+  Future<Owned<AuthorizationAcceptor>> authorizeTask =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_TASK);
+  Future<Owned<AuthorizationAcceptor>> authorizeExecutorInfo =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_EXECUTOR);
+  Future<IDAcceptor<FrameworkID>> selectFrameworkId =
+    IDAcceptor<FrameworkID>(request.url.query.get("framework_id"));
 
-  return collect(frameworksApprover, tasksApprover, executorsApprover)
+  return collect(
+      authorizeFrameworkInfo,
+      authorizeTask,
+      authorizeExecutorInfo,
+      selectFrameworkId)
     .then(defer(master->self(),
-        [this, request](const tuple<Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>>& approvers)
+        [this, request](const tuple<Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>,
+                                    IDAcceptor<FrameworkID>>& acceptors)
           -> Response {
       // This lambda is consumed before the outer lambda
       // returns, hence capture by reference is fine here.
-      auto frameworks = [this, &approvers](JSON::ObjectWriter* writer) {
-        // Get approver from tuple.
-        Owned<ObjectApprover> frameworksApprover;
-        Owned<ObjectApprover> tasksApprover;
-        Owned<ObjectApprover> executorsApprover;
-        tie(frameworksApprover, tasksApprover, executorsApprover) = approvers;
+      auto frameworks = [this, &acceptors](JSON::ObjectWriter* writer) {
+        Owned<AuthorizationAcceptor> authorizeFrameworkInfo;
+        Owned<AuthorizationAcceptor> authorizeTask;
+        Owned<AuthorizationAcceptor> authorizeExecutorInfo;
+        IDAcceptor<FrameworkID> selectFrameworkId;
+        tie(authorizeFrameworkInfo,
+            authorizeTask,
+            authorizeExecutorInfo,
+            selectFrameworkId) = acceptors;
 
         // Model all of the frameworks.
         writer->field(
             "frameworks",
-            [this, &frameworksApprover, &executorsApprover, &tasksApprover](
-                JSON::ArrayWriter* writer) {
-              foreachvalue (Framework* framework,
-                            master->frameworks.registered) {
-                // Skip unauthorized frameworks.
-                if (!approveViewFrameworkInfo(
-                        frameworksApprover, framework->info)) {
-                  continue;
-                }
+            [this,
+             &authorizeFrameworkInfo,
+             &authorizeTask,
+             &authorizeExecutorInfo,
+             &selectFrameworkId](JSON::ArrayWriter* writer) {
+          foreachvalue (Framework* framework, master->frameworks.registered) {
+            // Skip unauthorized frameworks or frameworks without a matching ID.
+            if (!selectFrameworkId.accept(framework->id()) ||
+                !authorizeFrameworkInfo->accept(framework->info)) {
+              continue;
+            }
 
-                FullFrameworkWriter frameworkWriter(
-                    tasksApprover,
-                    executorsApprover,
-                    framework);
+            FullFrameworkWriter frameworkWriter(
+                authorizeTask,
+                authorizeExecutorInfo,
+                framework);
 
-                writer->element(frameworkWriter);
-              }
-            });
+            writer->element(frameworkWriter);
+          }
+        });
 
         // Model all of the completed frameworks.
         writer->field(
             "completed_frameworks",
-            [this, &frameworksApprover, &executorsApprover, &tasksApprover](
-                JSON::ArrayWriter* writer) {
-              foreachvalue (const Owned<Framework>& framework,
-                            master->frameworks.completed) {
-                // Skip unauthorized frameworks.
-                if (!approveViewFrameworkInfo(
-                        frameworksApprover, framework->info)) {
-                  continue;
-                }
+            [this,
+             &authorizeFrameworkInfo,
+             &authorizeTask,
+             &authorizeExecutorInfo,
+             &selectFrameworkId](JSON::ArrayWriter* writer) {
+          foreachvalue (const Owned<Framework>& framework,
+                        master->frameworks.completed) {
+            // Skip unauthorized frameworks or frameworks without a matching ID.
+            if (!selectFrameworkId.accept(framework->id()) ||
+                !authorizeFrameworkInfo->accept(framework->info)) {
+              continue;
+            }
 
-                FullFrameworkWriter frameworkWriter(
-                    tasksApprover,
-                    executorsApprover,
-                    framework.get());
+            FullFrameworkWriter frameworkWriter(
+                authorizeTask,
+                authorizeExecutorInfo,
+                framework.get());
 
-                writer->element(frameworkWriter);
-              }
-            });
+            writer->element(frameworkWriter);
+          }
+        });
 
         // Unregistered frameworks are no longer possible. We emit an
         // empty array for the sake of backward compatibility.
@@ -2433,7 +2448,11 @@ string Master::Http::SLAVES_HELP()
         "",
         "This endpoint shows information about the agents which are registered",
         "in this master or recovered from registry, formatted as a JSON",
-        "object."),
+        "object.",
+        "",
+        "Query parameters:",
+        ">        slave_id=VALUE       The ID of the slave returned "
+        "(when no slave_id is specified, all slaves will be returned)."),
     AUTHENTICATION(true));
 }
 
@@ -2447,26 +2466,28 @@ Future<Response> Master::Http::slaves(
     return redirect(request);
   }
 
-  // Retrieve `ObjectApprover`s for authorizing roles.
-  Future<Owned<ObjectApprover>> rolesApprover;
-
-  if (master->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    rolesApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_ROLE);
-  } else {
-    rolesApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+  Future<Owned<AuthorizationAcceptor>> authorizeRole =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_ROLE);
+  Future<IDAcceptor<SlaveID>> selectSlaveId =
+    IDAcceptor<SlaveID>(request.url.query.get("slave_id"));
 
   Master* master = this->master;
   Option<string> jsonp = request.url.query.get("jsonp");
 
-  return rolesApprover.then(defer(master->self(),
-      [master, jsonp](const Owned<ObjectApprover>& rolesApprover)
+  return collect(authorizeRole, selectSlaveId)
+    .then(defer(master->self(),
+        [master, jsonp](const tuple<Owned<AuthorizationAcceptor>,
+                                    IDAcceptor<SlaveID>>& acceptors)
           -> Future<Response> {
-        return OK(jsonify(SlavesWriter(master->slaves, rolesApprover)), jsonp);
-      }));
+      Owned<AuthorizationAcceptor> authorizeRole;
+      IDAcceptor<SlaveID> selectSlaveId;
+      tie(authorizeRole, selectSlaveId) = acceptors;
+
+      return OK(
+          jsonify(SlavesWriter(master->slaves, authorizeRole, selectSlaveId)),
+          jsonp);
+  }));
 }
 
 
@@ -2745,66 +2766,49 @@ Future<Response> Master::Http::state(
     return redirect(request);
   }
 
-  // Retrieve `ObjectApprover`s for authorizing frameworks and tasks.
-  Future<Owned<ObjectApprover>> rolesApprover;
-  Future<Owned<ObjectApprover>> frameworksApprover;
-  Future<Owned<ObjectApprover>> tasksApprover;
-  Future<Owned<ObjectApprover>> executorsApprover;
-  Future<Owned<ObjectApprover>> flagsApprover;
-
-  if (master->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    rolesApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_ROLE);
-
-    frameworksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_FRAMEWORK);
-
-    tasksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_TASK);
-
-    executorsApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_EXECUTOR);
-
-    flagsApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_FLAGS);
-  } else {
-    rolesApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    flagsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+  Future<Owned<AuthorizationAcceptor>> authorizeRole =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_ROLE);
+  Future<Owned<AuthorizationAcceptor>> authorizeFrameworkInfo =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_FRAMEWORK);
+  Future<Owned<AuthorizationAcceptor>> authorizeTask =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_TASK);
+  Future<Owned<AuthorizationAcceptor>> authorizeExecutorInfo =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_EXECUTOR);
+  Future<Owned<AuthorizationAcceptor>> authorizeFlags =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_FLAGS);
 
   return collect(
-      rolesApprover,
-      frameworksApprover,
-      tasksApprover,
-      executorsApprover,
-      flagsApprover)
+      authorizeRole,
+      authorizeFrameworkInfo,
+      authorizeTask,
+      authorizeExecutorInfo,
+      authorizeFlags)
     .then(defer(
         master->self(),
-        [this, request](const tuple<Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>>& approvers)
+        [this, request](const tuple<Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>,
+                                    Owned<AuthorizationAcceptor>>& acceptors)
           -> Response {
       // This lambda is consumed before the outer lambda
       // returns, hence capture by reference is fine here.
-      auto state = [this, &approvers](JSON::ObjectWriter* writer) {
-        // Get approver from tuple.
-        Owned<ObjectApprover> rolesApprover;
-        Owned<ObjectApprover> frameworksApprover;
-        Owned<ObjectApprover> tasksApprover;
-        Owned<ObjectApprover> executorsApprover;
-        Owned<ObjectApprover> flagsApprover;
-        tie(rolesApprover,
-            frameworksApprover,
-            tasksApprover,
-            executorsApprover,
-            flagsApprover) = approvers;
+      auto state = [this, &acceptors](JSON::ObjectWriter* writer) {
+        Owned<AuthorizationAcceptor> authorizeRole;
+        Owned<AuthorizationAcceptor> authorizeFrameworkInfo;
+        Owned<AuthorizationAcceptor> authorizeTask;
+        Owned<AuthorizationAcceptor> authorizeExecutorInfo;
+        Owned<AuthorizationAcceptor> authorizeFlags;
+        tie(authorizeRole,
+            authorizeFrameworkInfo,
+            authorizeTask,
+            authorizeExecutorInfo,
+            authorizeFlags) = acceptors;
 
         writer->field("version", MESOS_VERSION);
 
@@ -2851,7 +2855,7 @@ Future<Response> Master::Http::state(
           });
         }
 
-        if (approveViewFlags(flagsApprover)) {
+        if (authorizeFlags->accept()) {
           if (master->flags.cluster.isSome()) {
             writer->field("cluster", master->flags.cluster.get());
           }
@@ -2877,9 +2881,9 @@ Future<Response> Master::Http::state(
 
         // Model all of the registered slaves.
         writer->field("slaves",
-          [this, rolesApprover](JSON::ArrayWriter* writer) {
+          [this, &authorizeRole](JSON::ArrayWriter* writer) {
             foreachvalue (Slave* slave, master->slaves.registered) {
-              writer->element(SlaveWriter(*slave, rolesApprover));
+              writer->element(SlaveWriter(*slave, authorizeRole));
             }
           });
 
@@ -2895,43 +2899,49 @@ Future<Response> Master::Http::state(
         // Model all of the frameworks.
         writer->field(
             "frameworks",
-            [this, &frameworksApprover, &executorsApprover, &tasksApprover](
-                JSON::ArrayWriter* writer) {
-              foreachvalue (
-                  Framework* framework,
-                  master->frameworks.registered) {
-                // Skip unauthorized frameworks.
-                if (!approveViewFrameworkInfo(
-                        frameworksApprover, framework->info)) {
-                  continue;
-                }
+            [this,
+             &authorizeFrameworkInfo,
+             &authorizeTask,
+             &authorizeExecutorInfo](JSON::ArrayWriter* writer) {
+          foreachvalue (
+              Framework* framework,
+              master->frameworks.registered) {
+            // Skip unauthorized frameworks.
+            if (!authorizeFrameworkInfo->accept(framework->info)) {
+              continue;
+            }
 
-                auto frameworkWriter = FullFrameworkWriter(
-                    tasksApprover, executorsApprover, framework);
+            auto frameworkWriter = FullFrameworkWriter(
+                authorizeTask,
+                authorizeExecutorInfo,
+                framework);
 
-                writer->element(frameworkWriter);
-              }
-            });
+            writer->element(frameworkWriter);
+          }
+        });
 
         // Model all of the completed frameworks.
         writer->field(
             "completed_frameworks",
-            [this, &frameworksApprover, &executorsApprover, &tasksApprover](
-                JSON::ArrayWriter* writer) {
-              foreachvalue (const Owned<Framework>& framework,
-                            master->frameworks.completed) {
-                // Skip unauthorized frameworks.
-                if (!approveViewFrameworkInfo(
-                    frameworksApprover, framework->info)) {
-                  continue;
-                }
+            [this,
+             &authorizeFrameworkInfo,
+             &authorizeTask,
+             &authorizeExecutorInfo](JSON::ArrayWriter* writer) {
+          foreachvalue (const Owned<Framework>& framework,
+                        master->frameworks.completed) {
+            // Skip unauthorized frameworks.
+            if (!authorizeFrameworkInfo->accept(framework->info)) {
+              continue;
+            }
 
-                auto frameworkWriter = FullFrameworkWriter(
-                    tasksApprover, executorsApprover, framework.get());
+            auto frameworkWriter = FullFrameworkWriter(
+                authorizeTask,
+                authorizeExecutorInfo,
+                framework.get());
 
-                writer->element(frameworkWriter);
-              }
-            });
+            writer->element(frameworkWriter);
+          }
+        });
 
         // Orphan tasks are no longer possible. We emit an empty array
         // for the sake of backward compatibility.
@@ -3218,31 +3228,22 @@ Future<Response> Master::Http::stateSummary(
     return redirect(request);
   }
 
-  Future<Owned<ObjectApprover>> rolesApprover;
-  Future<Owned<ObjectApprover>> frameworksApprover;
-
-  if (master->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    rolesApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_ROLE);
-    frameworksApprover = master->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_FRAMEWORK);
-  } else {
-    rolesApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-    frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+  Future<Owned<AuthorizationAcceptor>> authorizeRole =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_ROLE);
+  Future<Owned<AuthorizationAcceptor>> authorizeFrameworkInfo =
+    AuthorizationAcceptor::create(
+        principal, master->authorizer, authorization::VIEW_FRAMEWORK);
 
-  return collect(rolesApprover, frameworksApprover).then(defer(
+  return collect(authorizeRole, authorizeFrameworkInfo).then(defer(
       master->self(),
-      [this, request](const tuple<Owned<ObjectApprover>,
-                                    Owned<ObjectApprover>>& approvers)
+      [this, request](const tuple<Owned<AuthorizationAcceptor>,
+                                  Owned<AuthorizationAcceptor>>& acceptors)
           -> Response {
-        auto stateSummary = [this, &approvers](JSON::ObjectWriter* writer) {
-          Owned<ObjectApprover> rolesApprover;
-          Owned<ObjectApprover> frameworksApprover;
-          tie(rolesApprover,
-              frameworksApprover) = approvers;
+        auto stateSummary = [this, &acceptors](JSON::ObjectWriter* writer) {
+          Owned<AuthorizationAcceptor> authorizeRole;
+          Owned<AuthorizationAcceptor> authorizeFrameworkInfo;
+          tie(authorizeRole, authorizeFrameworkInfo) = acceptors;
 
           writer->field("hostname", master->info().hostname());
 
@@ -3271,14 +3272,14 @@ Future<Response> Master::Http::stateSummary(
               [this,
                &slaveFrameworkMapping,
                &taskStateSummaries,
-               &rolesApprover](JSON::ArrayWriter* writer) {
+               &authorizeRole](JSON::ArrayWriter* writer) {
                 foreachvalue (Slave* slave, master->slaves.registered) {
                   writer->element(
                       [&slave,
                        &slaveFrameworkMapping,
                        &taskStateSummaries,
-                       &rolesApprover](JSON::ObjectWriter* writer) {
-                        SlaveWriter slaveWriter(*slave, rolesApprover);
+                       &authorizeRole](JSON::ObjectWriter* writer) {
+                        SlaveWriter slaveWriter(*slave, authorizeRole);
                         slaveWriter(writer);
 
                         // Add the 'TaskState' summary for this slave.
@@ -3326,14 +3327,12 @@ Future<Response> Master::Http::stateSummary(
               [this,
                &slaveFrameworkMapping,
                &taskStateSummaries,
-               &frameworksApprover](JSON::ArrayWriter* writer) {
+               &authorizeFrameworkInfo](JSON::ArrayWriter* writer) {
                 foreachpair (const FrameworkID& frameworkId,
                              Framework* framework,
                              master->frameworks.registered) {
                   // Skip unauthorized frameworks.
-                  if (!approveViewFrameworkInfo(
-                          frameworksApprover,
-                          framework->info)) {
+                  if (!authorizeFrameworkInfo->accept(framework->info)) {
                     continue;
                   }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/8363449c/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 5824661..2d33f0b 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -2050,20 +2050,14 @@ Future<Response> Http::getContainers(
 {
   CHECK_EQ(mesos::agent::Call::GET_CONTAINERS, call.type());
 
-  Future<Owned<ObjectApprover>> approver;
-
-  if (slave->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    approver = slave->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_CONTAINER);
-  } else {
-    approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
-
-  return approver.then(defer(slave->self(), [this](
-    const Owned<ObjectApprover>& approver) {
-        return __containers(approver);
+  Future<Owned<AuthorizationAcceptor>> authorizeContainer =
+    AuthorizationAcceptor::create(
+        principal, slave->authorizer, authorization::VIEW_CONTAINER);
+
+  return authorizeContainer.then(defer(slave->self(),
+      [this](const Owned<AuthorizationAcceptor>& authorizeContainer) {
+        // Use an empty container ID filter.
+        return __containers(authorizeContainer, None());
     })).then([acceptType](const Future<JSON::Array>& result)
         -> Future<Response> {
       if (!result.isReady()) {
@@ -2089,22 +2083,23 @@ Future<Response> Http::_containers(
     const Request& request,
     const Option<Principal>& principal) const
 {
-  Future<Owned<ObjectApprover>> approver;
-
-  if (slave->authorizer.isSome()) {
-    Option<authorization::Subject> subject = createSubject(principal);
-
-    approver = slave->authorizer.get()->getObjectApprover(
-        subject, authorization::VIEW_CONTAINER);
-  } else {
-    approver = Owned<ObjectApprover>(new AcceptingObjectApprover());
-  }
+  Future<Owned<AuthorizationAcceptor>> authorizeContainer =
+    AuthorizationAcceptor::create(
+        principal, slave->authorizer, authorization::VIEW_CONTAINER);
+  Future<IDAcceptor<ContainerID>> selectContainerId =
+      IDAcceptor<ContainerID>(request.url.query.get("container_id"));
 
-  return approver.then(defer(slave->self(), [this](
-    const Owned<ObjectApprover>& approver) {
-      return __containers(approver);
-     }))
-     .then([request](const Future<JSON::Array>& result) -> Future<Response> {
+  return collect(authorizeContainer, selectContainerId)
+    .then(defer(
+        slave->self(),
+        [this](const tuple<Owned<AuthorizationAcceptor>,
+                           IDAcceptor<ContainerID>>& acceptors) {
+          Owned<AuthorizationAcceptor> authorizeContainer;
+          Option<IDAcceptor<ContainerID>> selectContainerId;
+          tie(authorizeContainer, selectContainerId) = acceptors;
+
+          return __containers(authorizeContainer, selectContainerId);
+    })).then([request](const Future<JSON::Array>& result) -> Future<Response> {
        if (!result.isReady()) {
          LOG(WARNING) << "Could not collect container status and statistics: "
                       << (result.isFailed()
@@ -2118,12 +2113,13 @@ Future<Response> Http::_containers(
 
        return process::http::OK(
            result.get(), request.url.query.get("jsonp"));
-     });
+    });
 }
 
 
 Future<JSON::Array> Http::__containers(
-    Option<Owned<ObjectApprover>> approver) const
+    Owned<AuthorizationAcceptor> authorizeContainer,
+    Option<IDAcceptor<ContainerID>> selectContainerId) const
 {
   Owned<list<JSON::Object>> metadata(new list<JSON::Object>());
   list<Future<ContainerStatus>> statusFutures;
@@ -2140,32 +2136,22 @@ Future<JSON::Array> Http::__containers(
       const ExecutorInfo& info = executor->info;
       const ContainerID& containerId = executor->containerId;
 
-      Try<bool> authorized = true;
-
-      if (approver.isSome()) {
-        ObjectApprover::Object object(info, framework->info);
-
-        authorized = approver.get()->approved(object);
-
-        if (authorized.isError()) {
-          LOG(WARNING) << "Error during ViewContainer authorization: "
-                       << authorized.error();
-          authorized = false;
-        }
+      if ((selectContainerId.isSome() &&
+           !selectContainerId->accept(containerId)) ||
+          !authorizeContainer->accept(info, framework->info)) {
+        continue;
       }
 
-      if (authorized.get()) {
-        JSON::Object entry;
-        entry.values["framework_id"] = info.framework_id().value();
-        entry.values["executor_id"] = info.executor_id().value();
-        entry.values["executor_name"] = info.name();
-        entry.values["source"] = info.source();
-        entry.values["container_id"] = containerId.value();
-
-        metadata->push_back(entry);
-        statusFutures.push_back(slave->containerizer->status(containerId));
-        statsFutures.push_back(slave->containerizer->usage(containerId));
-      }
+      JSON::Object entry;
+      entry.values["framework_id"] = info.framework_id().value();
+      entry.values["executor_id"] = info.executor_id().value();
+      entry.values["executor_name"] = info.name();
+      entry.values["source"] = info.source();
+      entry.values["container_id"] = containerId.value();
+
+      metadata->push_back(entry);
+      statusFutures.push_back(slave->containerizer->status(containerId));
+      statsFutures.push_back(slave->containerizer->usage(containerId));
     }
   }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/8363449c/src/slave/http.hpp
----------------------------------------------------------------------
diff --git a/src/slave/http.hpp b/src/slave/http.hpp
index ad412d4..44a95de 100644
--- a/src/slave/http.hpp
+++ b/src/slave/http.hpp
@@ -114,7 +114,8 @@ private:
 
   // Helper function to collect containers status and resource statistics.
   process::Future<JSON::Array> __containers(
-      Option<process::Owned<ObjectApprover>> approver) const;
+      process::Owned<AuthorizationAcceptor> authorizeContainer,
+      Option<IDAcceptor<ContainerID>> selectContainerId) const;
 
   // Helper routines for endpoint authorization.
   Try<std::string> extractEndpoint(const process::http::URL& url) const;


[3/3] mesos git commit: Added test cases for /slaves, /containers, /frameworks endpoints.

Posted by gr...@apache.org.
Added test cases for /slaves, /containers, /frameworks endpoints.

Added query parameter test cases for '/slaves' and '/frameworks' on
the master, and '/containers' on the agent.

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


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

Branch: refs/heads/master
Commit: 916a5c9fdbc7619b7c9356c21afb83e043feef88
Parents: 8363449
Author: Quinn Leng <qu...@gmail.com>
Authored: Tue Jul 18 17:07:02 2017 -0700
Committer: Greg Mann <gr...@gmail.com>
Committed: Tue Jul 18 17:11:34 2017 -0700

----------------------------------------------------------------------
 src/tests/master_tests.cpp | 317 +++++++++++++++++++++++++++++++++++++---
 src/tests/slave_tests.cpp  | 306 ++++++++++++++++++++++++++++----------
 2 files changed, 526 insertions(+), 97 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/916a5c9f/src/tests/master_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_tests.cpp b/src/tests/master_tests.cpp
index 6e6461c..5742860 100644
--- a/src/tests/master_tests.cpp
+++ b/src/tests/master_tests.cpp
@@ -2505,6 +2505,125 @@ TEST_F(MasterTest, SlavesEndpointTwoSlaves)
 }
 
 
+// Ensures that the '/slaves' endpoint returns the correct slave and it's in
+// the correct field of the response when provided with a slave ID query
+// parameter.
+TEST_F(MasterTest, SlavesEndpointQuerySlave)
+{
+  master::Flags masterFlags = CreateMasterFlags();
+
+  // Ensure that master can recover from the same work_dir.
+  masterFlags.registry = "replicated_log";
+  Try<Owned<cluster::Master>> master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+
+  // Start two agents.
+
+  Future<SlaveRegisteredMessage> slave1RegisteredMessage =
+    FUTURE_PROTOBUF(SlaveRegisteredMessage(), master.get()->pid, _);
+
+  Try<Owned<cluster::Slave>> slave1 = StartSlave(detector.get());
+  ASSERT_SOME(slave1);
+
+  AWAIT_READY(slave1RegisteredMessage);
+
+  Future<SlaveRegisteredMessage> slave2RegisteredMessage =
+    FUTURE_PROTOBUF(
+        SlaveRegisteredMessage(),
+        master.get()->pid,
+        Not(slave1.get()->pid));
+
+  Try<Owned<cluster::Slave>> slave2 = StartSlave(detector.get());
+  ASSERT_SOME(slave2);
+
+  AWAIT_READY(slave2RegisteredMessage);
+
+  // Query the information about the first agent.
+  {
+    string slaveId = slave1RegisteredMessage->slave_id().value();
+
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "slaves?slave_id=" + slaveId,
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+    AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
+
+    const Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
+
+    ASSERT_SOME(value);
+
+    Try<JSON::Object> object = value->as<JSON::Object>();
+
+    Result<JSON::Array> array = object->find<JSON::Array>("slaves");
+    ASSERT_SOME(array);
+    EXPECT_EQ(1u, array->values.size());
+
+    Try<JSON::Value> expected = JSON::parse(
+        "{"
+          "\"slaves\":"
+            "[{"
+                "\"id\":\"" + slaveId + "\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected);
+
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
+
+  // Stop agents while the master is down.
+  master->reset();
+  slave1.get()->terminate();
+  slave1->reset();
+  slave2.get()->terminate();
+  slave2->reset();
+
+  // Restart the master, now two agents should be in the 'recovered' state.
+  master = StartMaster(masterFlags);
+  ASSERT_SOME(master);
+
+  // Check if the second agent is in the 'recovered_slaves' field.
+  {
+    string slaveId = slave2RegisteredMessage->slave_id().value();
+
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "slaves?slave_id=" + slaveId,
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+    AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
+
+    const Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
+
+    ASSERT_SOME(value);
+    Try<JSON::Object> object = value->as<JSON::Object>();
+
+    Result<JSON::Array> array = object->find<JSON::Array>("recovered_slaves");
+    ASSERT_SOME(array);
+    EXPECT_EQ(1u, array->values.size());
+
+    Try<JSON::Value> expected = JSON::parse(
+        "{"
+          "\"recovered_slaves\":"
+            "[{"
+                "\"id\":\"" + slaveId + "\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected);
+
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
+}
+
+
 // This test ensures that when a slave is recovered from the registry
 // but does not re-register with the master, it is marked unreachable
 // in the registry, the framework is informed that the slave is lost,
@@ -5848,43 +5967,195 @@ TEST_F(MasterTest, FrameworksEndpointWithoutFrameworks)
 }
 
 
-TEST_F(MasterTest, FrameworksEndpointOneFramework)
+// Ensures that the '/master/frameworks' endpoint returns the correct framework
+// when provided with a framework ID query parameter.
+TEST_F(MasterTest, FrameworksEndpointMultipleFrameworks)
 {
   Try<Owned<cluster::Master>> master = StartMaster();
   ASSERT_SOME(master);
 
-  FrameworkInfo framework = DEFAULT_FRAMEWORK_INFO;
+  // Start a slave to receive shutdown message when framework is terminated.
+  Owned<MasterDetector> detector = master.get()->createDetector();
+  Try<Owned<cluster::Slave>> slave = StartSlave(detector.get());
+  ASSERT_SOME(slave);
 
-  MockScheduler sched;
-  MesosSchedulerDriver driver(
-      &sched, framework, master.get()->pid, DEFAULT_CREDENTIAL);
+  Future<RegisterSlaveMessage> registerSlaveMessage =
+    FUTURE_PROTOBUF(RegisterSlaveMessage(), _, _);
 
-  Future<Nothing> registered;
-  EXPECT_CALL(sched, registered(&driver, _, _))
-    .WillOnce(FutureSatisfy(&registered));
+  AWAIT_READY(registerSlaveMessage);
 
-  driver.start();
+  // Start two frameworks.
 
-  AWAIT_READY(registered);
+  Future<FrameworkID> frameworkId1;
+  Future<FrameworkID> frameworkId2;
 
-  Future<Response> response = process::http::get(
+  MockScheduler sched1;
+  MesosSchedulerDriver driver1(
+      &sched1,
+      DEFAULT_FRAMEWORK_INFO,
       master.get()->pid,
-      "frameworks",
-      None(),
-      createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+      DEFAULT_CREDENTIAL);
 
-  AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
-  AWAIT_EXPECT_RESPONSE_HEADER_EQ(APPLICATION_JSON, "Content-Type", response);
+  EXPECT_CALL(sched1, registered(_, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId1));
 
-  Try<JSON::Object> parse = JSON::parse<JSON::Object>(response->body);
-  ASSERT_SOME(parse);
+  // Ignore any incoming resource offers to the scheduler.
+  EXPECT_CALL(sched1, resourceOffers(_, _))
+    .WillRepeatedly(Return());
 
-  Result<JSON::Array> array = parse->find<JSON::Array>("frameworks");
-  ASSERT_SOME(array);
-  EXPECT_EQ(1u, array->values.size());
+  driver1.start();
 
-  driver.stop();
-  driver.join();
+  MockScheduler sched2;
+  MesosSchedulerDriver driver2(
+      &sched2,
+      DEFAULT_FRAMEWORK_INFO,
+      master.get()->pid,
+      DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched2, registered(_, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId2));
+
+  // Ignore any incoming resource offers to the scheduler.
+  EXPECT_CALL(sched2, resourceOffers(_, _))
+    .WillRepeatedly(Return());
+
+  driver2.start();
+
+  AWAIT_READY(frameworkId1);
+  AWAIT_READY(frameworkId2);
+
+  // Request with no query parameter.
+  {
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "frameworks",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    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> array = object.find<JSON::Array>("frameworks");
+    ASSERT_SOME(array);
+    EXPECT_EQ(2u, array->values.size());
+
+    Try<JSON::Value> frameworkJson1 = JSON::parse(
+        "{"
+            "\"id\":\"" + frameworkId1->value() + "\","
+            "\"name\":\"default\""
+        "}");
+
+    Try<JSON::Value> frameworkJson2 = JSON::parse(
+        "{"
+            "\"id\":\"" + frameworkId2->value() + "\","
+            "\"name\":\"default\""
+        "}");
+
+    ASSERT_SOME(frameworkJson1);
+    ASSERT_SOME(frameworkJson2);
+
+    // Since frameworks are stored in a hashmap, there is no strict guarantee of
+    // their ordering when listed. For this reason, we test both possibilities.
+    if (array->values[0].contains(frameworkJson1.get())) {
+      ASSERT_TRUE(array->values[1].contains(frameworkJson2.get()));
+    } else {
+      ASSERT_TRUE(array->values[0].contains(frameworkJson2.get()));
+      ASSERT_TRUE(array->values[1].contains(frameworkJson1.get()));
+    }
+  }
+
+  // Query the first framework.
+  {
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "frameworks?framework_id=" + frameworkId1->value(),
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    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> array = object.find<JSON::Array>("frameworks");
+    ASSERT_SOME(array);
+    EXPECT_EQ(1u, array->values.size());
+
+    Try<JSON::Value> expected = JSON::parse(
+        "{"
+          "\"frameworks\":"
+            "[{"
+                "\"id\":\"" + frameworkId1->value() + "\","
+                "\"name\":\"default\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected);
+
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
+
+  // Expect a teardown call and a shutdown message to ensure that the
+  // master has marked the framework as completed.
+  Future<mesos::scheduler::Call> teardownCall = FUTURE_CALL(
+      mesos::scheduler::Call(), mesos::scheduler::Call::TEARDOWN, _, _);
+  Future<ShutdownFrameworkMessage> shutdownFrameworkMessage =
+    FUTURE_PROTOBUF(ShutdownFrameworkMessage(), _, _);
+
+  // Complete the first framework. As a result, it will appear in the response's
+  // 'completed_frameworks' field.
+  driver1.stop();
+  driver1.join();
+
+  AWAIT_READY(teardownCall);
+
+  AWAIT_READY(shutdownFrameworkMessage);
+
+  // Query the first framework.
+  {
+    Future<Response> response = process::http::get(
+        master.get()->pid,
+        "frameworks?framework_id=" + frameworkId1->value(),
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    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> array =
+      object.find<JSON::Array>("completed_frameworks");
+    ASSERT_SOME(array);
+    EXPECT_EQ(1u, array->values.size());
+
+    Try<JSON::Value> expected = JSON::parse(
+        "{"
+          "\"completed_frameworks\":"
+            "[{"
+                "\"id\":\"" + frameworkId1->value() + "\","
+                "\"name\":\"default\""
+            "}]"
+        "}");
+
+    ASSERT_SOME(expected);
+
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
+
+  driver2.stop();
+  driver2.join();
 }
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/916a5c9f/src/tests/slave_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_tests.cpp b/src/tests/slave_tests.cpp
index 053a14d..e1cc96d 100644
--- a/src/tests/slave_tests.cpp
+++ b/src/tests/slave_tests.cpp
@@ -2358,30 +2358,39 @@ TEST_F(SlaveTest, ContainersEndpointNoExecutor)
 
 
 // This is an end-to-end test that verifies that the slave returns the
-// correct container status and resource statistics based on the
-// currently running executors, and the values returned by the
-// '/containers' endpoint are as expected.
+// correct container status and resource statistics based on the currently
+// running executors, and ensures that '/containers' endpoint returns the
+// correct container when it is provided a container ID query parameter.
 TEST_F(SlaveTest, ContainersEndpoint)
 {
   Try<Owned<cluster::Master>> master = StartMaster();
   ASSERT_SOME(master);
 
-  MockExecutor exec(DEFAULT_EXECUTOR_ID);
-  TestContainerizer containerizer(&exec);
-  StandaloneMasterDetector detector(master.get()->pid);
+  // Create two executors so that we can launch tasks in two separate
+  // containers.
+  ExecutorInfo executor1 = createExecutorInfo("executor-1", "exit 1");
+  ExecutorInfo executor2 = createExecutorInfo("executor-2", "exit 1");
 
-  MockSlave slave(CreateSlaveFlags(), &detector, &containerizer);
-  spawn(slave);
+  MockExecutor exec1(executor1.executor_id());
+  MockExecutor exec2(executor2.executor_id());
+
+  hashmap<ExecutorID, Executor*> execs;
+  execs[executor1.executor_id()] = &exec1;
+  execs[executor2.executor_id()] = &exec2;
+
+  TestContainerizer containerizer(execs);
+
+  Owned<MasterDetector> detector = master.get()->createDetector();
+  Try<Owned<cluster::Slave>> slave = StartSlave(detector.get(), &containerizer);
+  ASSERT_SOME(slave);
 
   MockScheduler sched;
   MesosSchedulerDriver driver(
       &sched, DEFAULT_FRAMEWORK_INFO, master.get()->pid, DEFAULT_CREDENTIAL);
 
-  EXPECT_CALL(sched, registered(_, _, _));
-  EXPECT_CALL(exec, registered(_, _, _, _));
+  EXPECT_CALL(sched, registered(&driver, _, _));
 
   Future<vector<Offer>> offers;
-
   EXPECT_CALL(sched, resourceOffers(&driver, _))
     .WillOnce(FutureArg<1>(&offers))
     .WillRepeatedly(Return()); // Ignore subsequent offers.
@@ -2389,98 +2398,247 @@ TEST_F(SlaveTest, ContainersEndpoint)
   driver.start();
 
   AWAIT_READY(offers);
-  EXPECT_NE(0u, offers->size());
+  ASSERT_NE(0u, offers->size());
 
-  const Offer& offer = offers.get()[0];
+  // Launch two tasks, each under a different executor.
+  vector<TaskInfo> tasks;
 
-  TaskInfo task = createTask(
-      offer.slave_id(),
-      Resources::parse("cpus:0.1;mem:32").get(),
-      SLEEP_COMMAND(1000),
-      exec.id);
+  TaskInfo task1;
+  {
+    task1.set_name("");
+    task1.mutable_task_id()->set_value("1");
+    task1.mutable_slave_id()->MergeFrom(offers->front().slave_id());
+    task1.mutable_resources()->MergeFrom(
+        Resources::parse("cpus:1;mem:512").get());
+    task1.mutable_executor()->MergeFrom(executor1);
+    tasks.push_back(task1);
+  }
 
-  EXPECT_CALL(exec, launchTask(_, _))
-    .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING));
+  TaskInfo task2;
+  {
+    task2.set_name("");
+    task2.mutable_task_id()->set_value("2");
+    task2.mutable_slave_id()->MergeFrom(offers->front().slave_id());
+    task2.mutable_resources()->MergeFrom(
+        Resources::parse("cpus:1;mem:512").get());
+    task2.mutable_executor()->MergeFrom(executor2);
+    tasks.push_back(task2);
+  }
 
-  Future<TaskStatus> status;
+  EXPECT_CALL(exec1, registered(_, _, _, _));
+
+  Future<TaskInfo> launchedTask1;
+  EXPECT_CALL(exec1, launchTask(_, _))
+    .WillOnce(DoAll(SendStatusUpdateFromTask(TASK_RUNNING),
+                    FutureArg<1>(&launchedTask1)));
+
+  EXPECT_CALL(exec2, registered(_, _, _, _));
+
+  Future<TaskInfo> launchedTask2;
+  EXPECT_CALL(exec2, launchTask(_, _))
+    .WillOnce(DoAll(SendStatusUpdateFromTask(TASK_RUNNING),
+                    FutureArg<1>(&launchedTask2)));
+
+  Future<TaskStatus> status1, status2;
   EXPECT_CALL(sched, statusUpdate(&driver, _))
-    .WillOnce(FutureArg<1>(&status));
+    .WillOnce(FutureArg<1>(&status1))
+    .WillOnce(FutureArg<1>(&status2));
 
-  driver.launchTasks(offer.id(), {task});
+  driver.launchTasks(offers->front().id(), tasks);
 
-  AWAIT_READY(status);
-  EXPECT_EQ(TASK_RUNNING, status->state());
+  AWAIT_READY(launchedTask1);
+  EXPECT_EQ(task1.task_id(), launchedTask1->task_id());
+
+  AWAIT_READY(launchedTask2);
+  EXPECT_EQ(task2.task_id(), launchedTask2->task_id());
+
+  AWAIT_READY(status1);
+  EXPECT_EQ(TASK_RUNNING, status1->state());
+
+  AWAIT_READY(status2);
+  EXPECT_EQ(TASK_RUNNING, status2->state());
+
+  // Prepare container statistics.
+  ResourceStatistics statistics1;
+  statistics1.set_mem_limit_bytes(2048);
 
-  ResourceStatistics statistics;
-  statistics.set_mem_limit_bytes(2048);
+  ResourceStatistics statistics2;
+  statistics2.set_mem_limit_bytes(2048);
 
+  // Get the container ID and return simulated statistics.
+  Future<ContainerID> containerId1;
+  Future<ContainerID> containerId2;
+
+  // Will be called twice during the first request. We extract the assigned
+  // container IDs for use when requesting information on a single container.
   EXPECT_CALL(containerizer, usage(_))
-    .WillOnce(Return(statistics));
+    .WillOnce(DoAll(FutureArg<0>(&containerId1), Return(statistics1)))
+    .WillOnce(DoAll(FutureArg<0>(&containerId2), Return(statistics2)));
 
-  ContainerStatus containerStatus;
+  // Construct the container statuses to be returned. Note that
+  // these container IDs will be different than the actual container
+  // IDs assigned by the agent, but creating them here allows us to
+  // easily confirm the output of '/containers'.
+  ContainerStatus containerStatus1;
+  ContainerStatus containerStatus2;
 
   ContainerID parent;
-  ContainerID child;
   parent.set_value("parent");
-  child.set_value("child");
-  child.mutable_parent()->CopyFrom(parent);
-  containerStatus.mutable_container_id()->CopyFrom(child);
 
-  CgroupInfo* cgroupInfo = containerStatus.mutable_cgroup_info();
-  CgroupInfo::NetCls* netCls = cgroupInfo->mutable_net_cls();
-  netCls->set_classid(42);
+  {
+    ContainerID child;
+    child.set_value("child1");
+    child.mutable_parent()->CopyFrom(parent);
+    containerStatus1.mutable_container_id()->CopyFrom(child);
+
+    CgroupInfo* cgroupInfo = containerStatus1.mutable_cgroup_info();
+    CgroupInfo::NetCls* netCls = cgroupInfo->mutable_net_cls();
+    netCls->set_classid(42);
+
+    NetworkInfo* networkInfo = containerStatus1.add_network_infos();
+    NetworkInfo::IPAddress* ipAddr = networkInfo->add_ip_addresses();
+    ipAddr->set_ip_address("192.168.1.20");
+  }
 
-  NetworkInfo* networkInfo = containerStatus.add_network_infos();
-  NetworkInfo::IPAddress* ipAddr = networkInfo->add_ip_addresses();
-  ipAddr->set_ip_address("192.168.1.20");
+  {
+    ContainerID child;
+    child.set_value("child2");
+    child.mutable_parent()->CopyFrom(parent);
+    containerStatus2.mutable_container_id()->CopyFrom(child);
+
+    CgroupInfo* cgroupInfo = containerStatus2.mutable_cgroup_info();
+    CgroupInfo::NetCls* netCls = cgroupInfo->mutable_net_cls();
+    netCls->set_classid(42);
+
+    NetworkInfo* networkInfo = containerStatus2.add_network_infos();
+    NetworkInfo::IPAddress* ipAddr = networkInfo->add_ip_addresses();
+    ipAddr->set_ip_address("192.168.1.21");
+  }
 
+  // Will be called twice during the first request.
   EXPECT_CALL(containerizer, status(_))
-    .WillOnce(Return(containerStatus));
+    .WillOnce(Return(containerStatus1))
+    .WillOnce(Return(containerStatus2));
 
-  Future<Response> response = process::http::get(
-      slave.self(),
-      "containers",
-      None(),
-      createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+  // Request information about all containers.
+  {
+    Future<Response> response = process::http::get(
+        slave.get()->pid,
+        "containers",
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
 
-  AWAIT_READY(response);
-  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::Array array = value->as<JSON::Array>();
+
+    EXPECT_TRUE(array.values.size() == 2);
+
+    Try<JSON::Value> containerJson1 = JSON::parse(
+        "{"
+            "\"executor_name\":\"\","
+            "\"source\":\"\","
+            "\"statistics\":{"
+                "\"mem_limit_bytes\":2048"
+            "},"
+            "\"status\":{"
+                "\"container_id\":{"
+                  "\"parent\":{\"value\":\"parent\"},"
+                  "\"value\":\"child1\""
+                "},"
+                "\"cgroup_info\":{\"net_cls\":{\"classid\":42}},"
+                "\"network_infos\":[{"
+                    "\"ip_addresses\":[{\"ip_address\":\"192.168.1.20\"}]"
+                "}]"
+            "}"
+          "}");
+
+    Try<JSON::Value> containerJson2 = JSON::parse(
+        "{"
+            "\"executor_name\":\"\","
+            "\"source\":\"\","
+            "\"statistics\":{"
+                "\"mem_limit_bytes\":2048"
+            "},"
+            "\"status\":{"
+                "\"container_id\":{"
+                  "\"parent\":{\"value\":\"parent\"},"
+                  "\"value\":\"child2\""
+                "},"
+                "\"cgroup_info\":{\"net_cls\":{\"classid\":42}},"
+                "\"network_infos\":[{"
+                    "\"ip_addresses\":[{\"ip_address\":\"192.168.1.21\"}]"
+                "}]"
+            "}"
+          "}");
+
+    // Since containers are stored in a hashmap, there is no strict guarantee of
+    // their ordering when listed. For this reason, we test both possibilities.
+    if (array.values[0].contains(containerJson1.get())) {
+      ASSERT_TRUE(array.values[1].contains(containerJson2.get()));
+    } else {
+      ASSERT_TRUE(array.values[0].contains(containerJson2.get()));
+      ASSERT_TRUE(array.values[1].contains(containerJson1.get()));
+    }
+  }
 
-  Try<JSON::Value> value = JSON::parse(response->body);
-  ASSERT_SOME(value);
+  AWAIT_READY(containerId1);
+  AWAIT_READY(containerId2);
 
-  Try<JSON::Value> expected = JSON::parse(
-      "[{"
-          "\"executor_id\":\"default\","
-          "\"executor_name\":\"\","
-          "\"source\":\"\","
-          "\"statistics\":{"
-              "\"mem_limit_bytes\":2048"
-          "},"
-          "\"status\":{"
+  // Will be called once during the second request.
+  EXPECT_CALL(containerizer, usage(_))
+    .WillOnce(Return(statistics1));
+
+  // Will be called once during the second request.
+  EXPECT_CALL(containerizer, status(_))
+    .WillOnce(Return(containerStatus1));
+
+  {
+    Future<Response> response = process::http::get(
+        slave.get()->pid,
+        "containers?container_id=" + containerId1->value(),
+        None(),
+        createBasicAuthHeaders(DEFAULT_CREDENTIAL));
+
+    Try<JSON::Value> value = JSON::parse<JSON::Value>(response->body);
+    ASSERT_SOME(value);
+
+    JSON::Array array = value->as<JSON::Array>();
+
+    EXPECT_TRUE(array.values.size() == 1);
+
+    Try<JSON::Value> expected = JSON::parse(
+        "[{"
+            "\"container_id\":\"" + containerId1->value() + "\","
+            "\"executor_name\":\"\","
+            "\"source\":\"\","
+            "\"statistics\":{"
+                "\"mem_limit_bytes\":2048"
+            "},"
+            "\"status\":{"
               "\"container_id\":{"
                 "\"parent\":{\"value\":\"parent\"},"
-                "\"value\":\"child\""
-              "},"
-              "\"cgroup_info\":{\"net_cls\":{\"classid\":42}},"
-              "\"network_infos\":[{"
-                  "\"ip_addresses\":[{\"ip_address\":\"192.168.1.20\"}]"
-              "}]"
-          "}"
-      "}]");
-
-  ASSERT_SOME(expected);
-  EXPECT_TRUE(value->contains(expected.get()));
+                  "\"value\":\"child1\""
+                "},"
+                "\"cgroup_info\":{\"net_cls\":{\"classid\":42}},"
+                "\"network_infos\":[{"
+                    "\"ip_addresses\":[{\"ip_address\":\"192.168.1.20\"}]"
+                "}]"
+            "}"
+        "}]");
+
+    ASSERT_SOME(expected);
+    EXPECT_TRUE(value->contains(expected.get()));
+  }
 
-  EXPECT_CALL(exec, shutdown(_))
+  EXPECT_CALL(exec1, shutdown(_))
+    .Times(AtMost(1));
+  EXPECT_CALL(exec2, shutdown(_))
     .Times(AtMost(1));
 
   driver.stop();
   driver.join();
-
-  terminate(slave);
-  wait(slave);
 }