You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by vi...@apache.org on 2014/02/15 02:17:33 UTC
[4/4] git commit: Added observe endpoint to master.
Added observe endpoint to master.
This changes adds a new HTTP endpoint of observe to master.
This allows clients to report health via an HTTP POST.
Values are:
MONITOR = Monitor for which health is being reported.
HOSTS = Comma seperated list of hosts.
LEVEL = OK for healthy, anything else for unhealthy.
This also contains a small fix to alphabetize the existing
endpoints / help strings.
Review: https://reviews.apache.org/r/17255
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/165dbe10
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/165dbe10
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/165dbe10
Branch: refs/heads/master
Commit: 165dbe101812f50780a802c8eea0dd5579678d96
Parents: 5c2f4f7
Author: Charlie Carson <ch...@gmail.com>
Authored: Fri Feb 14 17:07:35 2014 -0800
Committer: Vinod Kone <vi...@twitter.com>
Committed: Fri Feb 14 17:07:35 2014 -0800
----------------------------------------------------------------------
src/Makefile.am | 1 +
src/master/http.cpp | 99 ++++++++++++++++++++++
src/master/master.cpp | 9 +-
src/master/master.hpp | 13 ++-
src/tests/repair_tests.cpp | 176 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 291 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/165dbe10/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index cfd7416..768c66a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -870,6 +870,7 @@ mesos_tests_SOURCES = \
tests/paths_tests.cpp \
tests/protobuf_io_tests.cpp \
tests/registrar_tests.cpp \
+ tests/repair_tests.cpp \
tests/resource_offers_tests.cpp \
tests/resources_tests.cpp \
tests/sasl_tests.cpp \
http://git-wip-us.apache.org/repos/asf/mesos/blob/165dbe10/src/master/http.cpp
----------------------------------------------------------------------
diff --git a/src/master/http.cpp b/src/master/http.cpp
index 966eed6..6aeb257 100644
--- a/src/master/http.cpp
+++ b/src/master/http.cpp
@@ -22,6 +22,8 @@
#include <string>
#include <vector>
+#include <boost/array.hpp>
+
#include <mesos/mesos.hpp>
#include <mesos/resources.hpp>
@@ -278,6 +280,103 @@ Future<Response> Master::Http::health(const Request& request)
return OK();
}
+const static string HOSTS_KEY = "hosts";
+const static string LEVEL_KEY = "level";
+const static string MONITOR_KEY = "monitor";
+
+const string Master::Http::OBSERVE_HELP = HELP(
+ TLDR(
+ "Observe a monitor health state for host(s)."),
+ USAGE(
+ "/master/observe"),
+ DESCRIPTION(
+ "This endpoint receives information indicating host(s) ",
+ "health."
+ "",
+ "The following fields should be supplied in a POST:",
+ "1. " + MONITOR_KEY + " - name of the monitor that is being reported",
+ "2. " + HOSTS_KEY + " - comma seperated list of hosts",
+ "3. " + LEVEL_KEY + " - OK for healthy, anything else for unhealthy"));
+
+
+Try<string> getFormValue(
+ const string& key,
+ const hashmap<string, string>& values)
+{
+ Option<string> value = values.get(key);
+
+ if (value.isNone()) {
+ return Error("Missing value for '" + key + "'.");
+ }
+
+ // HTTP decode the value.
+ Try<string> decodedValue = http::decode(value.get());
+ if (decodedValue.isError()) {
+ return decodedValue;
+ }
+
+ // Treat empty string as an error.
+ if (decodedValue.isSome() && decodedValue.get().empty()) {
+ return Error("Empty string for '" + key + "'.");
+ }
+
+ return decodedValue.get();
+}
+
+
+Future<Response> Master::Http::observe(const Request& request)
+{
+ LOG(INFO) << "HTTP request for '" << request.path << "'";
+
+ hashmap<string, string> values =
+ process::http::query::parse(request.body);
+
+ // Build up a JSON object of the values we recieved and send them back
+ // down the wire as JSON for validation / confirmation.
+ JSON::Object response;
+
+ // TODO(ccarson): As soon as RepairCoordinator is introduced it will
+ // consume these values. We should revisit if we still want to send the
+ // JSON down the wire at that point.
+
+ // Add 'monitor'.
+ Try<string> monitor = getFormValue(MONITOR_KEY, values);
+ if (monitor.isError()) {
+ return BadRequest(monitor.error());
+ }
+ response.values[MONITOR_KEY] = monitor.get();
+
+ // Add 'hosts'.
+ Try<string> hostsString = getFormValue(HOSTS_KEY, values);
+ if (hostsString.isError()) {
+ return BadRequest(hostsString.error());
+ }
+
+ vector<string> hosts = strings::split(hostsString.get(), ",");
+ JSON::Array hostArray;
+ hostArray.values.assign(hosts.begin(), hosts.end());
+
+ response.values[HOSTS_KEY] = hostArray;
+
+ // Add 'isHealthy'.
+ Try<string> level = getFormValue(LEVEL_KEY, values);
+ if (level.isError()) {
+ return BadRequest(level.error());
+ }
+
+ bool isHealthy = strings::upper(level.get()) == "OK";
+
+ // TODO(ccarson): This is a workaround b/c currently a bool is coerced
+ // into a JSON::Double instead of a JSON::True or JSON::False when
+ // you assign to a JSON::Value.
+ //
+ // SEE: https://issues.apache.org/jira/browse/MESOS-939
+ response.values["isHealthy"] =
+ (isHealthy ? JSON::Value(JSON::True()) : JSON::False());
+
+ return OK(response);
+}
+
const string Master::Http::REDIRECT_HELP = HELP(
TLDR(
http://git-wip-us.apache.org/repos/asf/mesos/blob/165dbe10/src/master/master.cpp
----------------------------------------------------------------------
diff --git a/src/master/master.cpp b/src/master/master.cpp
index f24df23..f4f5e04 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -462,16 +462,19 @@ void Master::initialize()
route("/health",
Http::HEALTH_HELP,
lambda::bind(&Http::health, http, lambda::_1));
+ route("/observe",
+ Http::OBSERVE_HELP,
+ lambda::bind(&Http::observe, http, lambda::_1));
route("/redirect",
Http::REDIRECT_HELP,
lambda::bind(&Http::redirect, http, lambda::_1));
- route("/stats.json",
+ route("/roles.json",
None(),
- lambda::bind(&Http::stats, http, lambda::_1));
+ lambda::bind(&Http::roles, http, lambda::_1));
route("/state.json",
None(),
lambda::bind(&Http::state, http, lambda::_1));
- route("/roles.json",
+ route("/stats.json",
None(),
lambda::bind(&Http::roles, http, lambda::_1));
http://git-wip-us.apache.org/repos/asf/mesos/blob/165dbe10/src/master/master.hpp
----------------------------------------------------------------------
diff --git a/src/master/master.hpp b/src/master/master.hpp
index 00d630a..9d1b56c 100644
--- a/src/master/master.hpp
+++ b/src/master/master.hpp
@@ -280,20 +280,24 @@ private:
process::Future<process::http::Response> health(
const process::http::Request& request);
+ // /master/observe
+ process::Future<process::http::Response> observe(
+ const process::http::Request& request);
+
// /master/redirect
process::Future<process::http::Response> redirect(
const process::http::Request& request);
- // /master/stats.json
- process::Future<process::http::Response> stats(
+ // /master/roles.json
+ process::Future<process::http::Response> roles(
const process::http::Request& request);
// /master/state.json
process::Future<process::http::Response> state(
const process::http::Request& request);
- // /master/roles.json
- process::Future<process::http::Response> roles(
+ // /master/stats.json
+ process::Future<process::http::Response> stats(
const process::http::Request& request);
// /master/tasks.json
@@ -301,6 +305,7 @@ private:
const process::http::Request& request);
const static std::string HEALTH_HELP;
+ const static std::string OBSERVE_HELP;
const static std::string REDIRECT_HELP;
const static std::string TASKS_HELP;
http://git-wip-us.apache.org/repos/asf/mesos/blob/165dbe10/src/tests/repair_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/repair_tests.cpp b/src/tests/repair_tests.cpp
new file mode 100644
index 0000000..ba6f50a
--- /dev/null
+++ b/src/tests/repair_tests.cpp
@@ -0,0 +1,176 @@
+/**
+ * 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 <string>
+#include <vector>
+
+#include <process/future.hpp>
+#include <process/gtest.hpp>
+#include <process/http.hpp>
+#include <process/pid.hpp>
+#include <process/process.hpp>
+
+#include <stout/json.hpp>
+
+#include "tests/mesos.hpp"
+
+
+using namespace mesos;
+using namespace mesos::internal;
+using namespace mesos::internal::tests;
+
+using mesos::internal::master::Master;
+
+using process::Future;
+using process::PID;
+
+using process::http::BadRequest;
+using process::http::OK;
+using process::http::Response;
+
+using std::string;
+using std::vector;
+
+using testing::_;
+
+
+class HealthTest : public MesosTest {};
+
+
+struct JsonResponse
+{
+ string monitor;
+ vector<string> hosts;
+ bool isHealthy;
+};
+
+
+string stringify(const JsonResponse& response)
+{
+ JSON::Object object;
+ object.values["monitor"] = response.monitor;
+
+ JSON::Array hosts;
+ hosts.values.assign(response.hosts.begin(), response.hosts.end());
+ object.values["hosts"] = hosts;
+
+ // TODO(ccarson): This is a workaround b/c currently a bool is coerced
+ // into a JSON::Double instead of a JSON::True or JSON::False when
+ // you assign to a JSON::Value.
+ //
+ // SEE: https://issues.apache.org/jira/browse/MESOS-939
+ object.values["isHealthy"] =
+ response.isHealthy ? JSON::Value(JSON::True()) : JSON::False();
+
+ return stringify(object);
+}
+
+
+// Using macros instead of a helper function so that we get good line
+// numbers from the test run.
+#define VALIDATE_BAD_RESPONSE(response, error) \
+ AWAIT_READY(response); \
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(BadRequest().status, response); \
+ AWAIT_EXPECT_RESPONSE_BODY_EQ(error, response)
+
+#define VALIDATE_GOOD_RESPONSE(response, jsonResponse) \
+ AWAIT_READY(response); \
+ AWAIT_EXPECT_RESPONSE_HEADER_EQ( \
+ "application/json", \
+ "Content-Type", \
+ response); \
+ AWAIT_EXPECT_RESPONSE_STATUS_EQ(OK().status, response); \
+ AWAIT_EXPECT_RESPONSE_BODY_EQ(jsonResponse, response);
+
+
+TEST_F(HealthTest, ObserveEndpoint)
+{
+ Try<PID<Master> > master = StartMaster();
+ ASSERT_SOME(master);
+
+ // Empty get to the observe endpoint.
+ Future<Response> response = process::http::get(master.get(), "observe");
+ VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'.");
+
+ // Empty post to the observe endpoint.
+ response = process::http::post(master.get(), "observe");
+ VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'.");
+
+ // Query string is ignored.
+ response = process::http::post(master.get(), "observe?monitor=foo");
+ VALIDATE_BAD_RESPONSE(response, "Missing value for 'monitor'.");
+
+ // Malformed value causes error.
+ response = process::http::post(master.get(), "observe", "monitor=foo%");
+ VALIDATE_BAD_RESPONSE(response, "Malformed % escape in 'foo%': '%'");
+
+ // Empty value causes error.
+ response = process::http::post(master.get(), "observe", "monitor=");
+ VALIDATE_BAD_RESPONSE(response, "Empty string for 'monitor'.");
+
+ // Missing hosts.
+ response = process::http::post(master.get(), "observe", "monitor=a");
+ VALIDATE_BAD_RESPONSE(response, "Missing value for 'hosts'.");
+
+ // Missing level.
+ response = process::http::post(master.get(), "observe", "monitor=a&hosts=b");
+ VALIDATE_BAD_RESPONSE(response, "Missing value for 'level'.");
+
+ // Good request is successful.
+ JsonResponse expected;
+ expected.monitor = "a";
+ expected.hosts.push_back("b");
+ expected.isHealthy = true;
+
+ response =
+ process::http::post(master.get(), "observe", "monitor=a&hosts=b&level=ok");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ // ok is case-insensitive.
+ response =
+ process::http::post(master.get(), "observe", "monitor=a&hosts=b&level=Ok");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ response =
+ process::http::post(master.get(), "observe", "monitor=a&hosts=b&level=oK");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ response =
+ process::http::post(master.get(), "observe", "monitor=a&hosts=b&level=OK");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ // level != OK is unhealthy.
+ expected.isHealthy = false;
+ response =
+ process::http::post(
+ master.get(),
+ "observe",
+ "monitor=a&hosts=b&level=true");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ // Comma seperated hosts are parsed into an array.
+ expected.hosts.push_back("e");
+ response =
+ process::http::post(
+ master.get(),
+ "observe",
+ "monitor=a&hosts=b,e&level=true");
+ VALIDATE_GOOD_RESPONSE(response, stringify(expected) );
+
+ Shutdown();
+}