From 63d2eee0e3d9edaeb4f11a8efd36b845773a76b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 30 Jun 2017 12:57:16 +0300 Subject: [PATCH] MXS-1220: Add endpoint for set/clear of server status The server status can now be manipulated via the REST API. Added tests for the state manipulation. Fixed minor issues in related code. --- Documentation/REST-API/Resources-Server.md | 83 +++++++++++++++++----- include/maxscale/json_api.h | 11 +++ server/core/config_runtime.cc | 9 +-- server/core/json_api.cc | 14 ++++ server/core/resource.cc | 30 ++++++++ server/core/test/rest-api/test/errors.js | 2 +- server/core/test/rest-api/test/server.js | 48 +++++++++++++ 7 files changed, 172 insertions(+), 25 deletions(-) diff --git a/Documentation/REST-API/Resources-Server.md b/Documentation/REST-API/Resources-Server.md index 02b64891d..4821b36a1 100644 --- a/Documentation/REST-API/Resources-Server.md +++ b/Documentation/REST-API/Resources-Server.md @@ -501,29 +501,80 @@ Server is in use: Status: 403 Forbidden ``` -# **TODO:** Implement the following features - -### Get all connections to a server - -Get all connections that are connected to a server. +### Set server status ``` -GET /v1/servers/:name/connections +POST /v1/servers/:name/set +``` + +The _:name_ in the URI must map to a server name with all whitespace replaced +with hyphens. This endpoint requires that the `status` parameter is passed with +the request. The value of `status` must be one of the following values. + +|Value | Status Description | +|-----------|--------------------------------| +|master | Server is a Master | +|slave | Server is a Slave | +|maintenance| Server is put into maintenance | +|running | Server is up and running | +|synced | Server is a Galera node | +|ndb | Server is a NDBCluster node | +|stale | Server is a stale Master | + +For example, to set the server _db-server-1_ into maintenance mode, a request to +the following URL must be made: + +``` +POST /v1/servers/db-server-1/set?status=maintenance ``` #### Response -### Close all connections to a server - -Close all connections to a particular server. This will forcefully close all -backend connections. - -``` -DELETE /v1/servers/:name/connections -``` - -#### Response +OK: ``` Status: 204 No Content ``` + +Server not found: + +``` +Status: 404 Not Found +``` + +Missing or invalid parameter: + +``` +Status: 403 Forbidden +``` + +### Clear server status + +``` +POST /v1/servers/:name/clear +``` + +The _:name_ in the URI must map to a server name with all whitespace replaced +with hyphens. This endpoint requires that the `status` parameter is passed with +the request. The value of `status` must be one of the values defined in the +_set_ endpoint documentation. + +#### Response + +OK: + +``` +Status: 204 No Content +``` + +Server not found: + +``` +Status: 404 Not Found +``` + +Missing or invalid parameter: + +``` +Status: 403 Forbidden +``` diff --git a/include/maxscale/json_api.h b/include/maxscale/json_api.h index 042a59cbc..64439a762 100644 --- a/include/maxscale/json_api.h +++ b/include/maxscale/json_api.h @@ -86,8 +86,19 @@ json_t* mxs_json_self_link(const char* host, const char* path, const char* id); * * @param json JSON object * @param json_ptr JSON Pointer to object + * * @return Pointed value or NULL if no value is found */ json_t* mxs_json_pointer(json_t* json, const char* json_ptr); + +/** + * @brief Return a JSON formatted error + * + * @param err Error description + * + * @return The error as JSON + */ +json_t* mxs_json_error(const char* err); + MXS_END_DECLS diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index af61df5d5..b8710df3b 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -1622,14 +1622,7 @@ json_t* runtime_get_json_error() if (errmsg.length()) { - json_t* err = json_object(); - json_object_set_new(err, "detail", json_string(errmsg.c_str())); - - json_t* arr = json_array(); - json_array_append_new(arr, err); - - obj = json_object(); - json_object_set_new(obj, "errors", arr); + obj = mxs_json_error(errmsg.c_str()); } return obj; diff --git a/server/core/json_api.cc b/server/core/json_api.cc index 555af8fb4..3d20b5c91 100644 --- a/server/core/json_api.cc +++ b/server/core/json_api.cc @@ -155,3 +155,17 @@ json_t* mxs_json_self_link(const char* host, const char* path, const char* id) return links; } + +json_t* mxs_json_error(const char* message) +{ + json_t* err = json_object(); + json_object_set_new(err, "detail", json_string(message)); + + json_t* arr = json_array(); + json_array_append_new(arr, err); + + json_t* obj = json_object(); + json_object_set_new(obj, "errors", arr); + + return obj; +} diff --git a/server/core/resource.cc b/server/core/resource.cc index db37a2e42..879d8db9f 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -562,6 +562,34 @@ HttpResponse cb_delete_user(const HttpRequest& request) return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } +HttpResponse cb_set_server(const HttpRequest& request) +{ + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + int opt = server_map_status(request.get_option("status").c_str()); + + if (opt) + { + server_set_status(server, opt); + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Invalid or missing value for the `status` parameter")); +} + +HttpResponse cb_clear_server(const HttpRequest& request) +{ + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + int opt = server_map_status(request.get_option("status").c_str()); + + if (opt) + { + server_clear_status(server, opt); + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Invalid or missing value for the `status` parameter")); +} + HttpResponse cb_modulecmd(const HttpRequest& request) { std::string module = request.uri_part(2); @@ -699,6 +727,8 @@ public: "services", ":service", "listeners"))); m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet"))); m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "unix"))); + m_post.push_back(SResource(new Resource(cb_set_server, 3, "servers", ":server", "set"))); + m_post.push_back(SResource(new Resource(cb_clear_server, 3, "servers", ":server", "clear"))); /** For all module commands that modify state/data */ m_post.push_back(SResource(new Resource(cb_modulecmd, 4, "maxscale", "modules", ":module", "?"))); diff --git a/server/core/test/rest-api/test/errors.js b/server/core/test/rest-api/test/errors.js index a1d498048..3db9a95bf 100644 --- a/server/core/test/rest-api/test/errors.js +++ b/server/core/test/rest-api/test/errors.js @@ -5,7 +5,7 @@ describe("Errors", function() { before(startMaxScale) - it("error on invalid PUT request", function() + it("error on invalid PATCH request", function() { return request.patch(base_url + "/servers/server1", { json: {this_is: "a test"}}) .should.be.rejected diff --git a/server/core/test/rest-api/test/server.js b/server/core/test/rest-api/test/server.js index 4e0b86360..243eb222f 100644 --- a/server/core/test/rest-api/test/server.js +++ b/server/core/test/rest-api/test/server.js @@ -80,3 +80,51 @@ describe("Server Relationships", function() { after(stopMaxScale) }); + +describe("Server Status", function() { + before(startMaxScale) + + it("create new server", function() { + return request.post(base_url + "/servers/", {json: server }) + .should.be.fulfilled + }); + + it("set server into maintenance", function() { + return request.post(base_url + "/servers/" + server.data.id + "/set?status=maintenance") + .then(function(resp) { + return request.get(base_url + "/servers/" + server.data.id) + }) + .then(function(resp) { + var srv = JSON.parse(resp) + srv.data.attributes.status.should.match(/Maintenance/) + }) + }); + + it("clear maintenance", function() { + return request.post(base_url + "/servers/" + server.data.id + "/clear?status=maintenance") + .then(function(resp) { + return request.get(base_url + "/servers/" + server.data.id) + }) + .then(function(resp) { + var srv = JSON.parse(resp) + srv.data.attributes.status.should.not.match(/Maintenance/) + }) + }); + + it("set invalid status value", function() { + return request.post(base_url + "/servers/" + server.data.id + "/set?status=somethingstrange") + .should.be.rejected + }); + + it("clear invalid status value", function() { + return request.post(base_url + "/servers/" + server.data.id + "/clear?status=somethingstrange") + .should.be.rejected + }); + + it("destroy server", function() { + return request.delete(base_url + "/servers/" + server.data.id) + .should.be.fulfilled + }); + + after(stopMaxScale) +});