You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ar...@apache.org on 2017/09/04 15:53:07 UTC

[1/2] mesos git commit: Enabled filtering of resource allocations and reservations in agent.

Repository: mesos
Updated Branches:
  refs/heads/master dfe4fc84e -> b9aebbbca


Enabled filtering of resource allocations and reservations in agent.

Adds support of the `VIEW_ROLE` ACL to the results generated by the
`/state` endpoint in the agent for fields `reserved_resources_full`,
`reserved_resources` and `reserved_resources_allocated`.

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


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

Branch: refs/heads/master
Commit: a931bfb1bf06bb990664fabe78aae06d373db82e
Parents: dfe4fc8
Author: Andrei Budnik <ab...@mesosphere.com>
Authored: Mon Sep 4 17:09:32 2017 +0200
Committer: Alexander Rojas <al...@mesosphere.io>
Committed: Mon Sep 4 17:52:37 2017 +0200

----------------------------------------------------------------------
 src/slave/http.cpp | 53 +++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 42 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/a931bfb1/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index 7fb0948..3ea7829 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -1231,6 +1231,7 @@ Future<Response> Http::state(
   Future<Owned<ObjectApprover>> tasksApprover;
   Future<Owned<ObjectApprover>> executorsApprover;
   Future<Owned<ObjectApprover>> flagsApprover;
+  Future<Owned<ObjectApprover>> rolesApprover;
 
   if (slave->authorizer.isSome()) {
     Option<authorization::Subject> subject = createSubject(principal);
@@ -1246,23 +1247,29 @@ Future<Response> Http::state(
 
     flagsApprover = slave->authorizer.get()->getObjectApprover(
         subject, authorization::VIEW_FLAGS);
+
+    rolesApprover = slave->authorizer.get()->getObjectApprover(
+        subject, authorization::VIEW_ROLE);
   } else {
     frameworksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     tasksApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     executorsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
     flagsApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
+    rolesApprover = Owned<ObjectApprover>(new AcceptingObjectApprover());
   }
 
   return collect(
       frameworksApprover,
       tasksApprover,
       executorsApprover,
-      flagsApprover)
+      flagsApprover,
+      rolesApprover)
     .then(defer(
         slave->self(),
         [this, request](const tuple<Owned<ObjectApprover>,
                                     Owned<ObjectApprover>,
                                     Owned<ObjectApprover>,
+                                    Owned<ObjectApprover>,
                                     Owned<ObjectApprover>>& approvers)
           -> Response {
       // This lambda is consumed before the outer lambda
@@ -1273,10 +1280,12 @@ Future<Response> Http::state(
         Owned<ObjectApprover> tasksApprover;
         Owned<ObjectApprover> executorsApprover;
         Owned<ObjectApprover> flagsApprover;
+        Owned<ObjectApprover> rolesApprover;
         tie(frameworksApprover,
             tasksApprover,
             executorsApprover,
-            flagsApprover) = approvers;
+            flagsApprover,
+            rolesApprover) = approvers;
 
         writer->field("version", MESOS_VERSION);
 
@@ -1309,21 +1318,34 @@ Future<Response> Http::state(
         const Resources& totalResources = slave->totalResources;
 
         writer->field("resources", totalResources);
-        writer->field("reserved_resources", totalResources.reservations());
+        writer->field(
+            "reserved_resources",
+            [&totalResources, &rolesApprover](JSON::ObjectWriter* writer) {
+              foreachpair (const string& role,
+                           const Resources& resources,
+                           totalResources.reservations()) {
+                if (approveViewRole(rolesApprover, role)) {
+                  writer->field(role, resources);
+                }
+              }
+            });
+
         writer->field("unreserved_resources", totalResources.unreserved());
 
         writer->field(
             "reserved_resources_full",
-            [&totalResources](JSON::ObjectWriter* writer) {
+            [&totalResources, &rolesApprover](JSON::ObjectWriter* writer) {
               foreachpair (const string& role,
                            const Resources& resources,
                            totalResources.reservations()) {
-                writer->field(role, [&resources](JSON::ArrayWriter* writer) {
-                  foreach (Resource resource, resources) {
-                    convertResourceFormat(&resource, ENDPOINT);
-                    writer->element(JSON::Protobuf(resource));
-                  }
-                });
+                if (approveViewRole(rolesApprover, role)) {
+                  writer->field(role, [&resources](JSON::ArrayWriter* writer) {
+                    foreach (Resource resource, resources) {
+                      convertResourceFormat(&resource, ENDPOINT);
+                      writer->element(JSON::Protobuf(resource));
+                    }
+                  });
+                }
               }
             });
 
@@ -1345,7 +1367,16 @@ Future<Response> Http::state(
         }
 
         writer->field(
-            "reserved_resources_allocated", allocatedResources.reservations());
+            "reserved_resources_allocated",
+            [&allocatedResources, &rolesApprover](JSON::ObjectWriter* writer) {
+              foreachpair (const string& role,
+                           const Resources& resources,
+                           allocatedResources.reservations()) {
+                if (approveViewRole(rolesApprover, role)) {
+                  writer->field(role, resources);
+                }
+              }
+            });
 
         writer->field(
             "unreserved_resources_allocated", allocatedResources.unreserved());


[2/2] mesos git commit: Added test to verify filtering of resource reservations & allocations.

Posted by ar...@apache.org.
Added test to verify filtering of resource reservations & allocations.

This patch extends `SlaveAuthorizerTest.FilterStateEndpoint` test to
check that agent's `/state` endpoint shows resource reservations and
allocations based on the `VIEW_ROLE` permissions of the principal
doing the request.

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


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

Branch: refs/heads/master
Commit: b9aebbbca755eafae0480193c172dc67e90cc547
Parents: a931bfb
Author: Andrei Budnik <ab...@mesosphere.com>
Authored: Mon Sep 4 17:09:51 2017 +0200
Committer: Alexander Rojas <al...@mesosphere.io>
Committed: Mon Sep 4 17:52:52 2017 +0200

----------------------------------------------------------------------
 src/tests/slave_authorization_tests.cpp | 282 +++++++++++++++++++++------
 1 file changed, 227 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/b9aebbbc/src/tests/slave_authorization_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_authorization_tests.cpp b/src/tests/slave_authorization_tests.cpp
index 536a8ef..30eceae 100644
--- a/src/tests/slave_authorization_tests.cpp
+++ b/src/tests/slave_authorization_tests.cpp
@@ -101,12 +101,19 @@ TYPED_TEST_CASE(SlaveAuthorizerTest, AuthorizerTypes);
 
 // This test verifies that authorization based endpoint filtering
 // works correctly on the /state endpoint.
-// Both default users are allowed to to view high level frameworks, but only
+// Both default users are allowed to view high level frameworks, but only
 // one is allowed to view the tasks.
+// After launching a single task per each framework, one for role "superhero"
+// and the other for role "muggle", this test verifies that each of two
+// default users can view resource allocations and resource reservations for
+// corresponding allowed roles only.
 TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
 {
   ACLs acls;
 
+  const string roleSuperhero = "superhero";
+  const string roleMuggle = "muggle";
+
   {
     // Default principal can see all frameworks.
     mesos::ACL::ViewFramework* acl = acls.add_view_frameworks();
@@ -156,6 +163,28 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
     acl->mutable_users()->set_type(ACL::Entity::NONE);
   }
 
+  {
+    // Default principal can view "superhero" role only.
+    ACL::ViewRole* acl = acls.add_view_roles();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+    acl->mutable_roles()->add_values(roleSuperhero);
+
+    acl = acls.add_view_roles();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL.principal());
+    acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE);
+  }
+
+  {
+    // Second default principal can view "muggle" role only.
+    ACL::ViewRole* acl = acls.add_view_roles();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+    acl->mutable_roles()->add_values(roleMuggle);
+
+    acl = acls.add_view_roles();
+    acl->mutable_principals()->add_values(DEFAULT_CREDENTIAL_2.principal());
+    acl->mutable_roles()->set_type(mesos::ACL::Entity::NONE);
+  }
+
   // Create an `Authorizer` with the ACLs.
   Try<Authorizer*> create = TypeParam::create(parameterize(acls));
   ASSERT_SOME(create);
@@ -164,69 +193,146 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
   Try<Owned<cluster::Master>> master = this->StartMaster(authorizer.get());
   ASSERT_SOME(master);
 
-  // Register framework with user "bar".
-  FrameworkInfo frameworkInfo = DEFAULT_FRAMEWORK_INFO;
-  frameworkInfo.set_role("role");
-  frameworkInfo.set_user("bar");
+  // Register framework with user "bar" and role "superhero".
+  FrameworkInfo frameworkSuperhero = DEFAULT_FRAMEWORK_INFO;
+  frameworkSuperhero.set_name("framework-" + roleSuperhero);
+  frameworkSuperhero.set_role(roleSuperhero);
+  frameworkSuperhero.set_user("bar");
 
   // Create an executor with user "bar".
-  ExecutorInfo executor = createExecutorInfo("test-executor", "sleep 2");
-  executor.mutable_command()->set_user("bar");
-
-  MockExecutor exec(executor.executor_id());
-  TestContainerizer containerizer(&exec);
+  ExecutorInfo executorSuperhero =
+    createExecutorInfo("test-executor-" + roleSuperhero, "sleep 2");
+  executorSuperhero.mutable_command()->set_user("bar");
+  MockExecutor execSuperhero(executorSuperhero.executor_id());
+
+  // Register framework with user "foo" and role "muggle".
+  FrameworkInfo frameworkMuggle = DEFAULT_FRAMEWORK_INFO;
+  frameworkMuggle.set_name("framework-" + roleMuggle);
+  frameworkMuggle.set_principal(DEFAULT_CREDENTIAL_2.principal());
+  frameworkMuggle.set_role(roleMuggle);
+  frameworkMuggle.set_user("foo");
+
+  // Create an executor with user "foo".
+  ExecutorInfo executorMuggle =
+    createExecutorInfo("test-executor-" + roleMuggle, "sleep 2");
+  executorMuggle.mutable_command()->set_user("foo");
+  MockExecutor execMuggle(executorMuggle.executor_id());
+
+  TestContainerizer containerizer(
+      {{executorSuperhero.executor_id(), &execSuperhero},
+       {executorMuggle.executor_id(), &execMuggle}});
+
+  slave::Flags flags = this->CreateSlaveFlags();
+  // Statically reserve resources for each role.
+  flags.resources = "cpus(" + roleSuperhero + "):2;" + "cpus(" + roleMuggle +
+    "):3;mem(" + roleSuperhero + "):512;" + "mem(" + roleMuggle + "):1024;";
 
   Owned<MasterDetector> detector = master.get()->createDetector();
-  Try<Owned<cluster::Slave>> slave =
-      this->StartSlave(detector.get(), &containerizer, authorizer.get());
+  Try<Owned<cluster::Slave>> slave = this->StartSlave(
+      detector.get(), &containerizer, authorizer.get(), flags);
 
   ASSERT_SOME(slave);
 
-  MockScheduler sched;
-  MesosSchedulerDriver driver(
-      &sched, frameworkInfo, master.get()->pid, DEFAULT_CREDENTIAL);
+  MockScheduler schedSuperhero;
+  MesosSchedulerDriver driverSuperhero(
+      &schedSuperhero,
+      frameworkSuperhero,
+      master.get()->pid,
+      DEFAULT_CREDENTIAL);
 
-  EXPECT_CALL(exec, registered(_, _, _, _))
+  EXPECT_CALL(execSuperhero, registered(_, _, _, _))
     .Times(AtMost(1));
 
-  Future<Nothing> registered;
-  EXPECT_CALL(sched, registered(&driver, _, _))
-    .WillOnce(FutureSatisfy(&registered))
+  Future<FrameworkID> frameworkIdSuperhero;
+  EXPECT_CALL(schedSuperhero, registered(&driverSuperhero, _, _))
+    .WillOnce(FutureArg<1>(&frameworkIdSuperhero));
+
+  Future<vector<Offer>> offersSuperhero;
+  EXPECT_CALL(schedSuperhero, resourceOffers(&driverSuperhero, _))
+    .WillOnce(FutureArg<1>(&offersSuperhero))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  driverSuperhero.start();
+
+  AWAIT_READY(frameworkIdSuperhero);
+
+  AWAIT_READY(offersSuperhero);
+  EXPECT_FALSE(offersSuperhero->empty());
+
+  // Define a task which will run on executorSuperhero of frameworkSuperhero.
+  TaskInfo taskSuperhero;
+  taskSuperhero.set_name("test-" + roleSuperhero);
+  taskSuperhero.mutable_task_id()->set_value("1");
+  taskSuperhero.mutable_slave_id()->MergeFrom(
+      offersSuperhero.get()[0].slave_id());
+  taskSuperhero.mutable_resources()->MergeFrom(
+      offersSuperhero.get()[0].resources());
+  taskSuperhero.mutable_executor()->MergeFrom(executorSuperhero);
+
+  EXPECT_CALL(execSuperhero, launchTask(_, _))
+    .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
     .WillRepeatedly(Return());
 
-  Future<vector<Offer>> offers;
-  EXPECT_CALL(sched, resourceOffers(&driver, _))
-    .WillOnce(FutureArg<1>(&offers))
+  Future<TaskStatus> statusSuperhero;
+  EXPECT_CALL(schedSuperhero, statusUpdate(&driverSuperhero, _))
+    .WillOnce(FutureArg<1>(&statusSuperhero));
+
+  driverSuperhero.launchTasks(offersSuperhero.get()[0].id(), {taskSuperhero});
+
+  AWAIT_READY(statusSuperhero);
+  EXPECT_EQ(TASK_RUNNING, statusSuperhero->state());
+
+  MockScheduler schedMuggle;
+  MesosSchedulerDriver driverMuggle(
+      &schedMuggle,
+      frameworkMuggle,
+      master.get()->pid,
+      DEFAULT_CREDENTIAL_2);
+
+  EXPECT_CALL(execMuggle, registered(_, _, _, _))
+    .Times(AtMost(1));
+
+  Future<FrameworkID> frameworkIdMuggle;
+  EXPECT_CALL(schedMuggle, registered(&driverMuggle, _, _))
+    .WillOnce(FutureArg<1>(&frameworkIdMuggle));
+
+  Future<vector<Offer>> offersMuggle;
+  EXPECT_CALL(schedMuggle, resourceOffers(&driverMuggle, _))
+    .WillOnce(FutureArg<1>(&offersMuggle))
     .WillRepeatedly(Return()); // Ignore subsequent offers.
 
-  driver.start();
+  driverMuggle.start();
 
-  AWAIT_READY(registered);
+  AWAIT_READY(frameworkIdMuggle);
 
-  AWAIT_READY(offers);
-  EXPECT_FALSE(offers->empty());
+  AWAIT_READY(offersMuggle);
+  EXPECT_FALSE(offersMuggle->empty());
 
-  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(executor);
+  // Define a task which will run on executorMuggle of frameworkMuggle.
+  TaskInfo taskMuggle;
+  taskMuggle.set_name("test-" + roleMuggle);
+  taskMuggle.mutable_task_id()->set_value("2");
+  taskMuggle.mutable_slave_id()->MergeFrom(
+      offersMuggle.get()[0].slave_id());
+  taskMuggle.mutable_resources()->MergeFrom(
+      offersMuggle.get()[0].resources());
+  taskMuggle.mutable_executor()->MergeFrom(executorMuggle);
 
-  EXPECT_CALL(exec, launchTask(_, _))
+  EXPECT_CALL(execMuggle, launchTask(_, _))
     .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING))
     .WillRepeatedly(Return());
 
-  Future<TaskStatus> status;
-  EXPECT_CALL(sched, statusUpdate(&driver, _))
-    .WillOnce(FutureArg<1>(&status));
+  Future<TaskStatus> statusMuggle;
+  EXPECT_CALL(schedMuggle, statusUpdate(&driverMuggle, _))
+    .WillOnce(FutureArg<1>(&statusMuggle));
 
-  driver.launchTasks(offers.get()[0].id(), {task});
+  driverMuggle.launchTasks(offersMuggle.get()[0].id(), {taskMuggle});
 
-  AWAIT_READY(status);
-  EXPECT_EQ(TASK_RUNNING, status->state());
+  AWAIT_READY(statusMuggle);
+  ASSERT_EQ(TASK_RUNNING, statusMuggle->state());
 
-  // Retrieve endpoint with the user allowed to view the framework.
+  // Retrieve endpoint with the user allowed to view the frameworks.
+  // The default user allowed to view role "superhero" only.
   {
     Future<Response> response = http::get(
         slave.get()->pid,
@@ -245,20 +351,50 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
     ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
 
     JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
-    EXPECT_EQ(1u, frameworks.values.size());
+    EXPECT_EQ(2u, frameworks.values.size());
+
+    foreach (const JSON::Value& value, frameworks.values) {
+      JSON::Object framework = value.as<JSON::Object>();
+      EXPECT_FALSE(framework.values.empty());
+      ASSERT_TRUE(framework.values["executors"].is<JSON::Array>());
 
-    JSON::Object framework = frameworks.values.front().as<JSON::Object>();
-    ASSERT_TRUE(framework.values["executors"].is<JSON::Array>());
+      JSON::Array executors = framework.values["executors"].as<JSON::Array>();
+      EXPECT_EQ(1u, executors.values.size());
+
+      JSON::Object executor = executors.values.front().as<JSON::Object>();
+      EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size());
+    }
 
-    JSON::Array executors = framework.values["executors"].as<JSON::Array>();
-    EXPECT_EQ(1u, executors.values.size());
+    ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>());
 
-    JSON::Object executor = executors.values.front().as<JSON::Object>();
-    EXPECT_EQ(1u, executor.values["tasks"].as<JSON::Array>().values.size());
+    JSON::Object reserved_resources =
+      state.values["reserved_resources"].as<JSON::Object>();
+    EXPECT_TRUE(reserved_resources.values[roleSuperhero].is<JSON::Object>());
+    EXPECT_FALSE(reserved_resources.values[roleMuggle].is<JSON::Object>());
+
+    ASSERT_TRUE(
+        state.values["reserved_resources_allocated"].is<JSON::Object>());
+
+    JSON::Object reserved_resources_allocated =
+      state.values["reserved_resources_allocated"].as<JSON::Object>();
+    EXPECT_TRUE(
+        reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>());
+    EXPECT_FALSE(
+        reserved_resources_allocated.values[roleMuggle].is<JSON::Object>());
+
+    ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>());
+
+    JSON::Object reserved_resources_full =
+      state.values["reserved_resources_full"].as<JSON::Object>();
+    EXPECT_TRUE(
+        reserved_resources_full.values[roleSuperhero].is<JSON::Array>());
+    EXPECT_FALSE(
+        reserved_resources_full.values[roleMuggle].is<JSON::Array>());
   }
 
-  // Retrieve endpoint with the user allowed to view the framework,
-  // but not the executor.
+  // Retrieve endpoint with the user allowed to view the frameworks,
+  // but not the executors.
+  // The second default user allowed to view role "muggle" only.
   {
     Future<Response> response = http::get(
         slave.get()->pid,
@@ -276,17 +412,53 @@ TYPED_TEST(SlaveAuthorizerTest, FilterStateEndpoint)
     ASSERT_TRUE(state.values["frameworks"].is<JSON::Array>());
 
     JSON::Array frameworks = state.values["frameworks"].as<JSON::Array>();
-    EXPECT_EQ(1u, frameworks.values.size());
+    EXPECT_EQ(2u, frameworks.values.size());
 
-    JSON::Object framework = frameworks.values.front().as<JSON::Object>();
-    EXPECT_TRUE(framework.values["executors"].as<JSON::Array>().values.empty());
+    foreach (const JSON::Value& value, frameworks.values) {
+      JSON::Object framework = value.as<JSON::Object>();
+      EXPECT_FALSE(framework.values.empty());
+      EXPECT_TRUE(
+          framework.values["executors"].as<JSON::Array>().values.empty());
     }
 
-  EXPECT_CALL(exec, shutdown(_))
+    ASSERT_TRUE(state.values["reserved_resources"].is<JSON::Object>());
+
+    JSON::Object reserved_resources =
+      state.values["reserved_resources"].as<JSON::Object>();
+    EXPECT_TRUE(reserved_resources.values[roleMuggle].is<JSON::Object>());
+    EXPECT_FALSE(reserved_resources.values[roleSuperhero].is<JSON::Object>());
+
+    ASSERT_TRUE(
+        state.values["reserved_resources_allocated"].is<JSON::Object>());
+
+    JSON::Object reserved_resources_allocated =
+      state.values["reserved_resources_allocated"].as<JSON::Object>();
+    EXPECT_TRUE(
+        reserved_resources_allocated.values[roleMuggle].is<JSON::Object>());
+    EXPECT_FALSE(
+        reserved_resources_allocated.values[roleSuperhero].is<JSON::Object>());
+
+    ASSERT_TRUE(state.values["reserved_resources_full"].is<JSON::Object>());
+
+    JSON::Object reserved_resources_full =
+      state.values["reserved_resources_full"].as<JSON::Object>();
+    EXPECT_TRUE(
+        reserved_resources_full.values[roleMuggle].is<JSON::Array>());
+    EXPECT_FALSE(
+        reserved_resources_full.values[roleSuperhero].is<JSON::Array>());
+  }
+
+  EXPECT_CALL(execSuperhero, shutdown(_))
     .Times(AtMost(1));
 
-  driver.stop();
-  driver.join();
+  EXPECT_CALL(execMuggle, shutdown(_))
+    .Times(AtMost(1));
+
+  driverSuperhero.stop();
+  driverSuperhero.join();
+
+  driverMuggle.stop();
+  driverMuggle.join();
 }