From 461cd6afd9cb4560c1060acb3f25e364ad6e791e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sun, 7 May 2017 10:31:32 +0300 Subject: [PATCH] MXS-1220: Add creation of listeners via REST API Listeners can now be created via the REST API by doing a POST request to the service listener resource. --- include/maxscale/config.h | 19 +++-- server/core/config_runtime.cc | 85 +++++++++++++++++++---- server/core/maxscale/config_runtime.h | 10 +++ server/core/resource.cc | 15 ++++ server/core/test/rest-api/test/service.js | 23 ++++++ 5 files changed, 133 insertions(+), 19 deletions(-) diff --git a/include/maxscale/config.h b/include/maxscale/config.h index 5efb52991..2afffa84d 100644 --- a/include/maxscale/config.h +++ b/include/maxscale/config.h @@ -46,14 +46,19 @@ MXS_BEGIN_DECLS #define MXS_JSON_PTR_RELATIONSHIPS_MONITORS "/data/relationships/monitors/data" #define MXS_JSON_PTR_RELATIONSHIPS_FILTERS "/data/relationships/filters/data" -/** Server JSON Pointers */ -#define MXS_JSON_PTR_SRV_PORT MXS_JSON_PTR_PARAMETERS "/port" -#define MXS_JSON_PTR_SRV_ADDRESS MXS_JSON_PTR_PARAMETERS "/address" -#define MXS_JSON_PTR_SRV_PROTOCOL MXS_JSON_PTR_PARAMETERS "/protocol" -#define MXS_JSON_PTR_SRV_AUTHENTICATOR MXS_JSON_PTR_PARAMETERS "/authenticator" -#define MXS_JSON_PTR_SRV_AUTHENTICATOR_OPTIONS MXS_JSON_PTR_PARAMETERS "/authenticator_options" +/** Parameter value JSON Pointers */ +#define MXS_JSON_PTR_PARAM_PORT MXS_JSON_PTR_PARAMETERS "/port" +#define MXS_JSON_PTR_PARAM_ADDRESS MXS_JSON_PTR_PARAMETERS "/address" +#define MXS_JSON_PTR_PARAM_PROTOCOL MXS_JSON_PTR_PARAMETERS "/protocol" +#define MXS_JSON_PTR_PARAM_AUTHENTICATOR MXS_JSON_PTR_PARAMETERS "/authenticator" +#define MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS MXS_JSON_PTR_PARAMETERS "/authenticator_options" +#define MXS_JSON_PTR_PARAM_SSL_KEY MXS_JSON_PTR_PARAMETERS "/ssl_key" +#define MXS_JSON_PTR_PARAM_SSL_CERT MXS_JSON_PTR_PARAMETERS "/ssl_cert" +#define MXS_JSON_PTR_PARAM_SSL_CA_CERT MXS_JSON_PTR_PARAMETERS "/ssl_ca_cert" +#define MXS_JSON_PTR_PARAM_SSL_VERSION MXS_JSON_PTR_PARAMETERS "/ssl_version" +#define MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH MXS_JSON_PTR_PARAMETERS "/ssl_cert_verify_depth" -#define PTR_MON_MODULE "/data/attributes/module" +#define MXS_JSON_PTR_MODULE "/data/attributes/module" /** * Common configuration parameters names diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index eec8406f8..bd361a8d2 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -816,8 +816,8 @@ static inline const char* string_or_null(json_t* json, const char* path) static bool server_contains_required_fields(json_t* json) { json_t* id = mxs_json_pointer(json, MXS_JSON_PTR_ID); - json_t* port = mxs_json_pointer(json, MXS_JSON_PTR_SRV_PORT); - json_t* address = mxs_json_pointer(json, MXS_JSON_PTR_SRV_ADDRESS); + json_t* port = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT); + json_t* address = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS); return (id && json_is_string(id) && address && json_is_string(address) && @@ -869,6 +869,14 @@ static bool link_server_to_objects(SERVER* server, set& relations) return rval; } +static string json_int_to_string(json_t* json) +{ + char str[25]; // Enough to store any 64-bit integer value + int64_t i = json_integer_value(json); + snprintf(str, sizeof(str), "%ld", i); + return string(str); +} + SERVER* runtime_create_server_from_json(json_t* json) { SERVER* rval = NULL; @@ -876,22 +884,20 @@ SERVER* runtime_create_server_from_json(json_t* json) if (server_contains_required_fields(json)) { const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID)); - const char* address = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_SRV_ADDRESS)); + const char* address = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS)); /** The port needs to be in string format */ - char port[200]; // Enough to store any port value - int i = json_integer_value(mxs_json_pointer(json, MXS_JSON_PTR_SRV_PORT)); - snprintf(port, sizeof(port), "%d", i); + string port = json_int_to_string(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT)); /** Optional parameters */ - const char* protocol = string_or_null(json, MXS_JSON_PTR_SRV_PROTOCOL); - const char* authenticator = string_or_null(json, MXS_JSON_PTR_SRV_AUTHENTICATOR); - const char* authenticator_options = string_or_null(json, MXS_JSON_PTR_SRV_AUTHENTICATOR_OPTIONS); + const char* protocol = string_or_null(json, MXS_JSON_PTR_PARAM_PROTOCOL); + const char* authenticator = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR); + const char* authenticator_options = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS); set relations; if (extract_relations(json, relations, server_relation_types, server_relation_is_valid) && - runtime_create_server(name, address, port, protocol, authenticator, authenticator_options)) + runtime_create_server(name, address, port.c_str(), protocol, authenticator, authenticator_options)) { rval = server_find_by_unique_name(name); ss_dassert(rval); @@ -1000,7 +1006,7 @@ static bool validate_monitor_json(json_t* json) json_t* value; if ((value = mxs_json_pointer(json, MXS_JSON_PTR_ID)) && json_is_string(value) && - (value = mxs_json_pointer(json, PTR_MON_MODULE)) && json_is_string(value)) + (value = mxs_json_pointer(json, MXS_JSON_PTR_MODULE)) && json_is_string(value)) { set relations; if (extract_relations(json, relations, object_relation_types, object_relation_is_valid)) @@ -1056,7 +1062,7 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json) if (validate_monitor_json(json)) { const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID)); - const char* module = json_string_value(mxs_json_pointer(json, PTR_MON_MODULE)); + const char* module = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_MODULE)); if (runtime_create_monitor(name, module)) { @@ -1324,3 +1330,58 @@ bool runtime_alter_logs_from_json(json_t* json) return rval; } + +static bool validate_listener_json(json_t* json) +{ + bool rval = false; + json_t* param; + + if ((param = mxs_json_pointer(json, MXS_JSON_PTR_ID)) && json_is_string(param) && + (param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS)) && json_is_object(param)) + { + json_t* value; + + if ((value = mxs_json_pointer(param, CN_PORT)) && json_is_integer(value) && + (!(value = mxs_json_pointer(param, CN_ADDRESS)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_AUTHENTICATOR)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_AUTHENTICATOR_OPTIONS)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_SSL_KEY)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_SSL_CERT)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_SSL_CA_CERT)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_SSL_VERSION)) || json_is_string(value)) && + (!(value = mxs_json_pointer(param, CN_SSL_CERT_VERIFY_DEPTH)) || json_is_integer(value))) + { + rval = true; + } + } + + return rval; +} + +bool runtime_create_listener_from_json(SERVICE* service, json_t* json) +{ + bool rval = false; + + if (validate_listener_json(json)) + { + string port = json_int_to_string(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT)); + + const char* id = string_or_null(json, MXS_JSON_PTR_ID); + const char* address = string_or_null(json, MXS_JSON_PTR_PARAM_ADDRESS); + const char* protocol = string_or_null(json, MXS_JSON_PTR_PARAM_PROTOCOL); + const char* authenticator = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR); + const char* authenticator_options = string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS); + const char* ssl_key = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_KEY); + const char* ssl_cert = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT); + const char* ssl_ca_cert = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CA_CERT); + const char* ssl_version = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_VERSION); + const char* ssl_cert_verify_depth = string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH); + + rval = runtime_create_listener(service, id, address, port.c_str(), protocol, + authenticator, authenticator_options, + ssl_key, ssl_cert, ssl_ca_cert, ssl_version, + ssl_cert_verify_depth); + } + + return rval; +} diff --git a/server/core/maxscale/config_runtime.h b/server/core/maxscale/config_runtime.h index b5600efb2..4043251f0 100644 --- a/server/core/maxscale/config_runtime.h +++ b/server/core/maxscale/config_runtime.h @@ -238,6 +238,16 @@ bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json); */ bool runtime_alter_service_from_json(SERVICE* service, json_t* new_json); +/** + * @brief Create a listener from JSON + * + * @param service Service where the listener is created + * @param json JSON definition of the new listener + * + * @return True if the listener was successfully created and started + */ +bool runtime_create_listener_from_json(SERVICE* service, json_t* json); + /** * @brief Alter logging options using JSON * diff --git a/server/core/resource.cc b/server/core/resource.cc index 142ced7b3..bf51ebc05 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -190,6 +190,19 @@ HttpResponse cb_create_monitor(const HttpRequest& request) return HttpResponse(MHD_HTTP_FORBIDDEN); } +HttpResponse cb_create_service_listener(const HttpRequest& request) +{ + json_t* json = request.get_json(); + SERVICE* service = service_find(request.uri_part(1).c_str()); + + if (service && json && runtime_create_listener_from_json(service, json)) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN); +} + HttpResponse cb_alter_monitor(const HttpRequest& request) { json_t* json = request.get_json(); @@ -453,6 +466,8 @@ public: m_post.push_back(SResource(new Resource(cb_flush, 3, "maxscale", "logs", "flush"))); m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers"))); m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors"))); + m_post.push_back(SResource(new Resource(cb_create_service_listener, 3, + "services", ":service", "listeners"))); /** Update resources */ m_put.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server"))); diff --git a/server/core/test/rest-api/test/service.js b/server/core/test/rest-api/test/service.js index 5b51efd5d..2aac1fdff 100644 --- a/server/core/test/rest-api/test/service.js +++ b/server/core/test/rest-api/test/service.js @@ -62,5 +62,28 @@ describe("Service", function() { }) }); + it("create a listener", function() { + var listener = { + "links": { + "self": "http://localhost:8989/v1/services/RW-Split-Router/listeners" + }, + "data": { + "attributes": { + "parameters": { + "port": 4012, + "protocol": "MySQLClient", + "authenticator": "MySQLAuth", + "address": "127.0.0.1" + } + }, + "id": "RW-Split-Listener-2", + "type": "listeners" + } + } + + return request.post(base_url + "/services/RW-Split-Router/listeners", {json: listener}) + .should.be.fulfilled + }); + after(stopMaxScale) });