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(); +}
