Repository: mesos
Updated Branches:
  refs/heads/master 7ea2323e3 -> bb8375975


HTTP Authenticated '/shutdown' endpoint.

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


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

Branch: refs/heads/master
Commit: bb8375975e92ee722befb478ddc3b2541d1ccaa9
Parents: 7ea2323
Author: Isabel Jimenez <[email protected]>
Authored: Mon Jul 7 14:38:31 2014 -0700
Committer: Benjamin Hindman <[email protected]>
Committed: Mon Jul 7 14:45:43 2014 -0700

----------------------------------------------------------------------
 src/Makefile.am              |   1 +
 src/master/http.cpp          | 156 +++++++++++++++++++---------
 src/master/master.cpp        |  17 +--
 src/master/master.hpp        |  11 +-
 src/slave/http.cpp           |  68 ++++++------
 src/slave/slave.cpp          |   2 +-
 src/slave/slave.hpp          |   4 +-
 src/tests/shutdown_tests.cpp | 213 ++++++++++++++++++++++++++++++++++++++
 8 files changed, 377 insertions(+), 95 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 45afcd1..2c7bfc5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1039,6 +1039,7 @@ mesos_tests_SOURCES =                             \
   tests/sasl_tests.cpp                         \
   tests/scheduler_tests.cpp                    \
   tests/script.cpp                             \
+  tests/shutdown_tests.cpp                     \
   tests/slave_recovery_tests.cpp               \
   tests/slave_tests.cpp                                \
   tests/sorter_tests.cpp                       \

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 5d86976..4fba007 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -28,6 +28,7 @@
 
 #include <process/metrics/metrics.hpp>
 
+#include <stout/base64.hpp>
 #include <stout/foreach.hpp>
 #include <stout/json.hpp>
 #include <stout/lambda.hpp>
@@ -63,6 +64,7 @@ using process::http::InternalServerError;
 using process::http::NotFound;
 using process::http::OK;
 using process::http::TemporaryRedirect;
+using process::http::Unauthorized;
 
 using process::metrics::internal::MetricsProcess;
 
@@ -323,7 +325,9 @@ Future<Response> Master::Http::redirect(const Request& 
request)
   LOG(INFO) << "HTTP request for '" << request.path << "'";
 
   // If there's no leader, redirect to this master's base url.
-  MasterInfo info = master.leader.isSome() ? master.leader.get() : 
master.info_;
+  MasterInfo info = master->leader.isSome()
+    ? master->leader.get()
+    : master->info_;
 
   Try<string> hostname =
     info.has_hostname() ? info.hostname() : net::getHostname(info.ip());
@@ -349,30 +353,30 @@ Future<Response> Master::Http::stats(const Request& 
request)
   LOG(INFO) << "HTTP request for '" << request.path << "'";
 
   JSON::Object object;
-  object.values["uptime"] = (Clock::now() - master.startTime).secs();
-  object.values["elected"] = master.elected() ? 1 : 0;
-  object.values["total_schedulers"] = master.frameworks.activated.size();
-  object.values["active_schedulers"] = master.getActiveFrameworks().size();
-  object.values["activated_slaves"] = master.slaves.activated.size();
-  object.values["deactivated_slaves"] = master.slaves.deactivated.size();
-  object.values["outstanding_offers"] = master.offers.size();
+  object.values["uptime"] = (Clock::now() - master->startTime).secs();
+  object.values["elected"] = master->elected() ? 1 : 0;
+  object.values["total_schedulers"] = master->frameworks.activated.size();
+  object.values["active_schedulers"] = master->getActiveFrameworks().size();
+  object.values["activated_slaves"] = master->slaves.activated.size();
+  object.values["deactivated_slaves"] = master->slaves.deactivated.size();
+  object.values["outstanding_offers"] = master->offers.size();
 
   // NOTE: These are monotonically increasing counters.
-  object.values["staged_tasks"] = master.stats.tasks[TASK_STAGING];
-  object.values["started_tasks"] = master.stats.tasks[TASK_STARTING];
-  object.values["finished_tasks"] = master.stats.tasks[TASK_FINISHED];
-  object.values["killed_tasks"] = master.stats.tasks[TASK_KILLED];
-  object.values["failed_tasks"] = master.stats.tasks[TASK_FAILED];
-  object.values["lost_tasks"] = master.stats.tasks[TASK_LOST];
-  object.values["valid_status_updates"] = master.stats.validStatusUpdates;
-  object.values["invalid_status_updates"] = master.stats.invalidStatusUpdates;
+  object.values["staged_tasks"] = master->stats.tasks[TASK_STAGING];
+  object.values["started_tasks"] = master->stats.tasks[TASK_STARTING];
+  object.values["finished_tasks"] = master->stats.tasks[TASK_FINISHED];
+  object.values["killed_tasks"] = master->stats.tasks[TASK_KILLED];
+  object.values["failed_tasks"] = master->stats.tasks[TASK_FAILED];
+  object.values["lost_tasks"] = master->stats.tasks[TASK_LOST];
+  object.values["valid_status_updates"] = master->stats.validStatusUpdates;
+  object.values["invalid_status_updates"] = master->stats.invalidStatusUpdates;
 
   // Get a count of all active tasks in the cluster i.e., the tasks
   // that are launched (TASK_STAGING, TASK_STARTING, TASK_RUNNING) but
   // haven't reached terminal state yet.
   // NOTE: This is a gauge representing an instantaneous value.
   int active_tasks = 0;
-  foreachvalue (Framework* framework, master.frameworks.activated) {
+  foreachvalue (Framework* framework, master->frameworks.activated) {
     active_tasks += framework->tasks.size();
   }
   object.values["active_tasks_gauge"] = active_tasks;
@@ -381,7 +385,7 @@ Future<Response> Master::Http::stats(const Request& request)
   // compute capacity of scalar resources.
   Resources totalResources;
   Resources usedResources;
-  foreachvalue (Slave* slave, master.slaves.activated) {
+  foreachvalue (Slave* slave, master->slaves.activated) {
     // Instead of accumulating all types of resources (which is
     // not necessary), we only accumulate scalar resources. This
     // helps us bypass a performance problem caused by range
@@ -469,39 +473,39 @@ Future<Response> Master::Http::state(const Request& 
request)
   object.values["build_date"] = build::DATE;
   object.values["build_time"] = build::TIME;
   object.values["build_user"] = build::USER;
-  object.values["start_time"] = master.startTime.secs();
+  object.values["start_time"] = master->startTime.secs();
 
-  if (master.electedTime.isSome()) {
-    object.values["elected_time"] = master.electedTime.get().secs();
+  if (master->electedTime.isSome()) {
+    object.values["elected_time"] = master->electedTime.get().secs();
   }
 
-  object.values["id"] = master.info().id();
-  object.values["pid"] = string(master.self());
-  object.values["hostname"] = master.info().hostname();
-  object.values["activated_slaves"] = master.slaves.activated.size();
-  object.values["deactivated_slaves"] = master.slaves.deactivated.size();
-  object.values["staged_tasks"] = master.stats.tasks[TASK_STAGING];
-  object.values["started_tasks"] = master.stats.tasks[TASK_STARTING];
-  object.values["finished_tasks"] = master.stats.tasks[TASK_FINISHED];
-  object.values["killed_tasks"] = master.stats.tasks[TASK_KILLED];
-  object.values["failed_tasks"] = master.stats.tasks[TASK_FAILED];
-  object.values["lost_tasks"] = master.stats.tasks[TASK_LOST];
+  object.values["id"] = master->info().id();
+  object.values["pid"] = string(master->self());
+  object.values["hostname"] = master->info().hostname();
+  object.values["activated_slaves"] = master->slaves.activated.size();
+  object.values["deactivated_slaves"] = master->slaves.deactivated.size();
+  object.values["staged_tasks"] = master->stats.tasks[TASK_STAGING];
+  object.values["started_tasks"] = master->stats.tasks[TASK_STARTING];
+  object.values["finished_tasks"] = master->stats.tasks[TASK_FINISHED];
+  object.values["killed_tasks"] = master->stats.tasks[TASK_KILLED];
+  object.values["failed_tasks"] = master->stats.tasks[TASK_FAILED];
+  object.values["lost_tasks"] = master->stats.tasks[TASK_LOST];
 
-  if (master.flags.cluster.isSome()) {
-    object.values["cluster"] = master.flags.cluster.get();
+  if (master->flags.cluster.isSome()) {
+    object.values["cluster"] = master->flags.cluster.get();
   }
 
-  if (master.leader.isSome()) {
-    object.values["leader"] = master.leader.get().pid();
+  if (master->leader.isSome()) {
+    object.values["leader"] = master->leader.get().pid();
   }
 
-  if (master.flags.log_dir.isSome()) {
-    object.values["log_dir"] = master.flags.log_dir.get();
+  if (master->flags.log_dir.isSome()) {
+    object.values["log_dir"] = master->flags.log_dir.get();
   }
 
   JSON::Object flags;
-  foreachpair (const string& name, const flags::Flag& flag, master.flags) {
-    Option<string> value = flag.stringify(master.flags);
+  foreachpair (const string& name, const flags::Flag& flag, master->flags) {
+    Option<string> value = flag.stringify(master->flags);
     if (value.isSome()) {
       flags.values[name] = value.get();
     }
@@ -511,7 +515,7 @@ Future<Response> Master::Http::state(const Request& request)
   // Model all of the slaves.
   {
     JSON::Array array;
-    foreachvalue (Slave* slave, master.slaves.activated) {
+    foreachvalue (Slave* slave, master->slaves.activated) {
       array.values.push_back(model(*slave));
     }
 
@@ -521,7 +525,7 @@ Future<Response> Master::Http::state(const Request& request)
   // Model all of the frameworks.
   {
     JSON::Array array;
-    foreachvalue (Framework* framework, master.frameworks.activated) {
+    foreachvalue (Framework* framework, master->frameworks.activated) {
       array.values.push_back(model(*framework));
     }
 
@@ -533,7 +537,7 @@ Future<Response> Master::Http::state(const Request& request)
     JSON::Array array;
 
     foreach (const memory::shared_ptr<Framework>& framework,
-             master.frameworks.completed) {
+             master->frameworks.completed) {
       array.values.push_back(model(*framework));
     }
 
@@ -545,12 +549,12 @@ Future<Response> Master::Http::state(const Request& 
request)
     JSON::Array array;
 
     // Find those orphan tasks.
-    foreachvalue (const Slave* slave, master.slaves.activated) {
+    foreachvalue (const Slave* slave, master->slaves.activated) {
       typedef hashmap<TaskID, Task*> TaskMap;
       foreachvalue (const TaskMap& tasks, slave->tasks) {
         foreachvalue (const Task* task, tasks) {
           CHECK_NOTNULL(task);
-          if (!master.frameworks.activated.contains(task->framework_id())) {
+          if (!master->frameworks.activated.contains(task->framework_id())) {
             array.values.push_back(model(*task));
           }
         }
@@ -567,9 +571,9 @@ Future<Response> Master::Http::state(const Request& request)
     JSON::Array array;
 
     // Find unregistered frameworks.
-    foreachvalue (const Slave* slave, master.slaves.activated) {
+    foreachvalue (const Slave* slave, master->slaves.activated) {
       foreachkey (const FrameworkID& frameworkId, slave->tasks) {
-        if (!master.frameworks.activated.contains(frameworkId)) {
+        if (!master->frameworks.activated.contains(frameworkId)) {
           array.values.push_back(frameworkId.value());
         }
       }
@@ -591,7 +595,7 @@ Future<Response> Master::Http::roles(const Request& request)
   // Model all of the roles.
   {
     JSON::Array array;
-    foreachvalue (Role* role, master.roles) {
+    foreachvalue (Role* role, master->roles) {
       array.values.push_back(model(*role));
     }
 
@@ -602,6 +606,58 @@ Future<Response> Master::Http::roles(const Request& 
request)
 }
 
 
+const string Master::Http::SHUTDOWN_HELP = HELP(
+    TLDR(
+        "Shuts down a running framework."),
+    USAGE(
+        "/master/shutdown"),
+    DESCRIPTION(
+        "Please provide a \"frameworkId\" value designating the ",
+        "running framework to shut down.",
+        "Returns 200 OK if the framework was correctly shutdown."));
+
+
+Future<Response> Master::Http::shutdown(const Request& request)
+{
+  if (master->credentials.isNone()) {
+    return Unauthorized("Mesos master");
+  }
+  hashmap<string, string> values =
+    process::http::query::parse(request.body);
+  Option<string> frameworkId = values.get("frameworkId");
+  if (frameworkId.isNone()) {
+    return BadRequest();
+  }
+  FrameworkID id;
+  id.set_value(frameworkId.get());
+  Framework* framework = master->getFramework(id);
+
+  Option<string> authHeader = request.headers.get("Authorization");
+  if (authHeader.isNone()) {
+    return Unauthorized("Mesos master");
+  }
+  const string& decodedAuth =
+    base64::decode(strings::split(authHeader.get(), " ", 2)[1]);
+  const std::vector<string>& pairs = strings::split(decodedAuth, ":", 2);
+  if (pairs.size() != 2) {
+    return Unauthorized("Mesos master");
+  }
+
+  const string& username = pairs[0];
+  const string& password = pairs[1];
+
+  foreach (const Credential& credential, master->credentials.get().http()) {
+    if (credential.principal() == username &&
+        (!credential.has_secret() || credential.secret() == password)) {
+      // TODO(ijimenez) make removeFramework asynchronously
+      master->removeFramework(framework);
+      return OK();
+    }
+  }
+  return Unauthorized("Mesos master");
+}
+
+
 const string Master::Http::TASKS_HELP = HELP(
     TLDR(
       "Lists tasks from all active frameworks."),
@@ -680,11 +736,11 @@ Future<Response> Master::Http::tasks(const Request& 
request)
 
   // Construct framework list with both active and completed framwworks.
   vector<const Framework*> frameworks;
-  foreachvalue (Framework* framework, master.frameworks.activated) {
+  foreachvalue (Framework* framework, master->frameworks.activated) {
     frameworks.push_back(framework);
   }
   foreach (const memory::shared_ptr<Framework>& framework,
-           master.frameworks.completed) {
+           master->frameworks.completed) {
     frameworks.push_back(framework.get());
   }
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/master/master.cpp
----------------------------------------------------------------------
diff --git a/src/master/master.cpp b/src/master/master.cpp
index 251b699..86b147f 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -233,7 +233,7 @@ Master::Master(
     const Option<Authorizer*>& _authorizer,
     const Flags& _flags)
   : ProcessBase("master"),
-    http(*this),
+    http(this),
     flags(_flags),
     allocator(_allocator),
     registrar(_registrar),
@@ -337,16 +337,18 @@ void Master::initialize()
     const string& path =
       strings::remove(flags.credentials.get(), "file://", strings::PREFIX);
 
-    Result<Credentials> credentials = credentials::read(path);
-    if (credentials.isError()) {
-      EXIT(1) << credentials.error() << " (see --credentials flag)";
-    } else if (credentials.isNone()) {
+    Result<Credentials> _credentials = credentials::read(path);
+    if (_credentials.isError()) {
+      EXIT(1) << _credentials.error() << " (see --credentials flag)";
+    } else if (_credentials.isNone()) {
       EXIT(1) << "Credentials file must contain at least one credential"
               << " (see --credentials flag)";
     }
+    // Store credentials in master to use them in routes.
+    credentials = _credentials.get();
 
     // Load "registration" credentials into SASL based Authenticator.
-    sasl::secrets::load(credentials.get());
+    sasl::secrets::load(_credentials.get());
 
   } else if (flags.authenticate_frameworks || flags.authenticate_slaves) {
     EXIT(1) << "Authentication requires a credentials file"
@@ -574,6 +576,9 @@ void Master::initialize()
   route("/roles.json",
         None(),
         lambda::bind(&Http::roles, http, lambda::_1));
+  route("/shutdown",
+        Http::SHUTDOWN_HELP,
+        lambda::bind(&Http::shutdown, http, lambda::_1));
   route("/state.json",
         None(),
         lambda::bind(&Http::state, http, lambda::_1));

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index 5fef354..8641f2d 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -370,13 +370,15 @@ protected:
   OfferID newOfferId();
   SlaveID newSlaveId();
 
+  Option<Credentials> credentials;
+
 private:
   // Inner class used to namespace HTTP route handlers (see
   // master/http.cpp for implementations).
   class Http
   {
   public:
-    explicit Http(const Master& _master) : master(_master) {}
+    explicit Http(Master* _master) : master(_master) {}
 
     // /master/health
     process::Future<process::http::Response> health(
@@ -394,6 +396,10 @@ private:
     process::Future<process::http::Response> roles(
         const process::http::Request& request);
 
+    // /master/shutdown
+    process::Future<process::http::Response> shutdown(
+        const process::http::Request& request);
+
     // /master/state.json
     process::Future<process::http::Response> state(
         const process::http::Request& request);
@@ -409,10 +415,11 @@ private:
     const static std::string HEALTH_HELP;
     const static std::string OBSERVE_HELP;
     const static std::string REDIRECT_HELP;
+    const static std::string SHUTDOWN_HELP;
     const static std::string TASKS_HELP;
 
   private:
-    const Master& master;
+    Master* master;
   } http;
 
   Master(const Master&);              // No copying.

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/slave/http.cpp
----------------------------------------------------------------------
diff --git a/src/slave/http.cpp b/src/slave/http.cpp
index cd7f692..98bdab6 100644
--- a/src/slave/http.cpp
+++ b/src/slave/http.cpp
@@ -245,20 +245,20 @@ Future<Response> Slave::Http::stats(const Request& 
request)
   LOG(INFO) << "HTTP request for '" << request.path << "'";
 
   JSON::Object object;
-  object.values["uptime"] = (Clock::now() - slave.startTime).secs();
-  object.values["total_frameworks"] = slave.frameworks.size();
-  object.values["registered"] = slave.master.isSome() ? "1" : "0";
-  object.values["recovery_errors"] = slave.recoveryErrors;
+  object.values["uptime"] = (Clock::now() - slave->startTime).secs();
+  object.values["total_frameworks"] = slave->frameworks.size();
+  object.values["registered"] = slave->master.isSome() ? "1" : "0";
+  object.values["recovery_errors"] = slave->recoveryErrors;
 
   // NOTE: These are monotonically increasing counters.
-  object.values["staged_tasks"] = slave.stats.tasks[TASK_STAGING];
-  object.values["started_tasks"] = slave.stats.tasks[TASK_STARTING];
-  object.values["finished_tasks"] = slave.stats.tasks[TASK_FINISHED];
-  object.values["killed_tasks"] = slave.stats.tasks[TASK_KILLED];
-  object.values["failed_tasks"] = slave.stats.tasks[TASK_FAILED];
-  object.values["lost_tasks"] = slave.stats.tasks[TASK_LOST];
-  object.values["valid_status_updates"] = slave.stats.validStatusUpdates;
-  object.values["invalid_status_updates"] = slave.stats.invalidStatusUpdates;
+  object.values["staged_tasks"] = slave->stats.tasks[TASK_STAGING];
+  object.values["started_tasks"] = slave->stats.tasks[TASK_STARTING];
+  object.values["finished_tasks"] = slave->stats.tasks[TASK_FINISHED];
+  object.values["killed_tasks"] = slave->stats.tasks[TASK_KILLED];
+  object.values["failed_tasks"] = slave->stats.tasks[TASK_FAILED];
+  object.values["lost_tasks"] = slave->stats.tasks[TASK_LOST];
+  object.values["valid_status_updates"] = slave->stats.validStatusUpdates;
+  object.values["invalid_status_updates"] = slave->stats.invalidStatusUpdates;
 
   // NOTE: These are gauges representing instantaneous values.
 
@@ -268,7 +268,7 @@ Future<Response> Slave::Http::stats(const Request& request)
   // Sent to executor (TASK_STAGING, TASK_STARTING, TASK_RUNNING).
   int launched_tasks = 0;
 
-  foreachvalue (Framework* framework, slave.frameworks) {
+  foreachvalue (Framework* framework, slave->frameworks) {
     foreachvalue (Executor* executor, framework->executors) {
       queued_tasks += executor->queuedTasks.size();
       launched_tasks += executor->launchedTasks.size();
@@ -337,45 +337,45 @@ Future<Response> Slave::Http::state(const Request& 
request)
   object.values["build_date"] = build::DATE;
   object.values["build_time"] = build::TIME;
   object.values["build_user"] = build::USER;
-  object.values["start_time"] = slave.startTime.secs();
-  object.values["id"] = slave.info.id().value();
-  object.values["pid"] = string(slave.self());
-  object.values["hostname"] = slave.info.hostname();
-  object.values["resources"] = model(slave.resources);
-  object.values["attributes"] = model(slave.attributes);
-  object.values["staged_tasks"] = slave.stats.tasks[TASK_STAGING];
-  object.values["started_tasks"] = slave.stats.tasks[TASK_STARTING];
-  object.values["finished_tasks"] = slave.stats.tasks[TASK_FINISHED];
-  object.values["killed_tasks"] = slave.stats.tasks[TASK_KILLED];
-  object.values["failed_tasks"] = slave.stats.tasks[TASK_FAILED];
-  object.values["lost_tasks"] = slave.stats.tasks[TASK_LOST];
-
-  if (slave.master.isSome()) {
-    Try<string> masterHostname = net::getHostname(slave.master.get().ip);
+  object.values["start_time"] = slave->startTime.secs();
+  object.values["id"] = slave->info.id().value();
+  object.values["pid"] = string(slave->self());
+  object.values["hostname"] = slave->info.hostname();
+  object.values["resources"] = model(slave->resources);
+  object.values["attributes"] = model(slave->attributes);
+  object.values["staged_tasks"] = slave->stats.tasks[TASK_STAGING];
+  object.values["started_tasks"] = slave->stats.tasks[TASK_STARTING];
+  object.values["finished_tasks"] = slave->stats.tasks[TASK_FINISHED];
+  object.values["killed_tasks"] = slave->stats.tasks[TASK_KILLED];
+  object.values["failed_tasks"] = slave->stats.tasks[TASK_FAILED];
+  object.values["lost_tasks"] = slave->stats.tasks[TASK_LOST];
+
+  if (slave->master.isSome()) {
+    Try<string> masterHostname = net::getHostname(slave->master.get().ip);
     if (masterHostname.isSome()) {
       object.values["master_hostname"] = masterHostname.get();
     }
   }
 
-  if (slave.flags.log_dir.isSome()) {
-    object.values["log_dir"] = slave.flags.log_dir.get();
+  if (slave->flags.log_dir.isSome()) {
+    object.values["log_dir"] = slave->flags.log_dir.get();
   }
 
   JSON::Array frameworks;
-  foreachvalue (Framework* framework, slave.frameworks) {
+  foreachvalue (Framework* framework, slave->frameworks) {
     frameworks.values.push_back(model(*framework));
   }
   object.values["frameworks"] = frameworks;
 
   JSON::Array completedFrameworks;
-  foreach (const Owned<Framework>& framework, slave.completedFrameworks) {
+  foreach (const Owned<Framework>& framework, slave->completedFrameworks) {
     completedFrameworks.values.push_back(model(*framework));
   }
   object.values["completed_frameworks"] = completedFrameworks;
 
   JSON::Object flags;
-  foreachpair (const string& name, const flags::Flag& flag, slave.flags) {
-    Option<string> value = flag.stringify(slave.flags);
+  foreachpair (const string& name, const flags::Flag& flag, slave->flags) {
+    Option<string> value = flag.stringify(slave->flags);
     if (value.isSome()) {
       flags.values[name] = value.get();
     }

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/slave/slave.cpp
----------------------------------------------------------------------
diff --git a/src/slave/slave.cpp b/src/slave/slave.cpp
index f42ab60..e81abb2 100644
--- a/src/slave/slave.cpp
+++ b/src/slave/slave.cpp
@@ -107,7 +107,7 @@ Slave::Slave(const slave::Flags& _flags,
              Files* _files)
   : ProcessBase(process::ID::generate("slave")),
     state(RECOVERING),
-    http(*this),
+    http(this),
     flags(_flags),
     completedFrameworks(MAX_COMPLETED_FRAMEWORKS),
     detector(_detector),

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/slave/slave.hpp
----------------------------------------------------------------------
diff --git a/src/slave/slave.hpp b/src/slave/slave.hpp
index 605ee4a..a896bb6 100644
--- a/src/slave/slave.hpp
+++ b/src/slave/slave.hpp
@@ -321,7 +321,7 @@ private:
   class Http
   {
   public:
-    explicit Http(const Slave& _slave) : slave(_slave) {}
+    explicit Http(Slave* _slave) : slave(_slave) {}
 
     // /slave/health
     process::Future<process::http::Response> health(
@@ -338,7 +338,7 @@ private:
     static const std::string HEALTH_HELP;
 
   private:
-    const Slave& slave;
+    Slave* slave;
   } http;
 
   friend struct Framework;

http://git-wip-us.apache.org/repos/asf/mesos/blob/bb837597/src/tests/shutdown_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/shutdown_tests.cpp b/src/tests/shutdown_tests.cpp
new file mode 100644
index 0000000..ad13aa1
--- /dev/null
+++ b/src/tests/shutdown_tests.cpp
@@ -0,0 +1,213 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+
+#include <string>
+
+#include <mesos/executor.hpp>
+#include <mesos/scheduler.hpp>
+
+#include <process/future.hpp>
+#include <process/gmock.hpp>
+#include <process/http.hpp>
+#include <process/pid.hpp>
+
+#include <stout/base64.hpp>
+#include <stout/hashmap.hpp>
+#include <stout/option.hpp>
+
+#include "master/flags.hpp"
+#include "master/master.hpp"
+
+#include "tests/mesos.hpp"
+#include "tests/utils.hpp"
+
+using std::string;
+
+using namespace mesos;
+using namespace mesos::internal;
+using namespace mesos::internal::slave;
+using namespace mesos::internal::tests;
+
+using mesos::internal::master::Master;
+using mesos::internal::slave::Slave;
+
+using process::Future;
+using process::PID;
+
+using process::http::BadRequest;
+using process::http::OK;
+using process::http::Response;
+using process::http::Unauthorized;
+
+using testing::_;
+using testing::Eq;
+using testing::SaveArg;
+using testing::Return;
+
+class ShutdownTest : public MesosTest {};
+
+// Testing /master/shutdown so this endopoint  shuts down
+// designated framework or return adequate error
+
+// Testing route with authorization header and good credentials
+TEST_F(ShutdownTest, ShutdownEndpoint)
+{
+  Try<PID<Master> > master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
+
+  ASSERT_EQ(DRIVER_RUNNING, driver.start());
+
+  AWAIT_READY(frameworkId);
+
+  hashmap<string, string> headers;
+  headers["Authorization"] = "Basic " +
+    base64::encode(DEFAULT_CREDENTIAL.principal() +
+                   ":" + DEFAULT_CREDENTIAL.secret());
+
+  Future<Response> response = process::http::post(
+      master.get(),
+      "shutdown",
+      headers,
+      "frameworkId=" + frameworkId.get().value());
+
+  AWAIT_READY(response);
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response);
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}
+
+
+// Testing route with bad credentials
+TEST_F(ShutdownTest, ShutdownEndpointBadCredentials)
+{
+  Try<PID<Master> > master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
+
+  ASSERT_EQ(DRIVER_RUNNING, driver.start());
+
+  AWAIT_READY(frameworkId);
+
+  hashmap<string, string> headers;
+  headers["Authorization"] = "Basic " +
+    base64::encode("badPrincipal:badSecret");
+
+  Future<Response> response = process::http::post(
+      master.get(),
+      "shutdown",
+      headers,
+      "frameworkId=" + frameworkId.get().value());
+
+  AWAIT_READY(response);
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+      Unauthorized("Mesos master").status,
+      response);
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}
+
+
+// Testing route without frameworkId value
+TEST_F(ShutdownTest, ShutdownEndpointNoFrameworkId)
+{
+  Try<PID<Master> > master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
+
+  ASSERT_EQ(DRIVER_RUNNING, driver.start());
+
+  AWAIT_READY(frameworkId);
+  hashmap<string, string> headers;
+  headers["Authorization"] = "Basic " +
+    base64::encode("badPrincipal:badSecret");
+  Future<Response> response =
+    process::http::post(master.get(), "shutdown", headers, "");
+  AWAIT_READY(response);
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response);
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}
+
+
+// Testing route without authorization header
+TEST_F(ShutdownTest, ShutdownEndpointNoHeader)
+{
+  Try<PID<Master> > master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  Future<FrameworkID> frameworkId;
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .WillOnce(FutureArg<1>(&frameworkId));
+
+  ASSERT_EQ(DRIVER_RUNNING, driver.start());
+
+  AWAIT_READY(frameworkId);
+    
+  Future<Response> response = process::http::post(
+      master.get(),
+      "shutdown",
+      None(),
+      "frameworkId=" + frameworkId.get().value());
+
+  AWAIT_READY(response);
+  AWAIT_EXPECT_RESPONSE_STATUS_EQ(
+      Unauthorized("Mesos master").status,
+      response);
+
+  driver.stop();
+  driver.join();
+
+  Shutdown();
+}

Reply via email to