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