From 6918842585fd9fb678d67ef779081719ec5a2c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 23 Oct 2017 12:53:38 +0300 Subject: [PATCH] Add direct relationship updating to REST API The JSON API specification states that all resources must support direct modification of resource relationships by providing only the definition for a particular relationship type to a /:type/:id/relationships/:type endpoint. The relevant part of the JSON API specification: http://jsonapi.org/format/#crud-updating-to-many-relationships --- server/core/config_runtime.cc | 85 +++++++++++++++++++++++++-- server/core/maxscale/config_runtime.h | 31 ++++++++++ server/core/resource.cc | 59 +++++++++++++++++++ 3 files changed, 171 insertions(+), 4 deletions(-) diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index f116c5dfb..5c4b2ca48 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -1415,11 +1415,46 @@ bool runtime_alter_server_from_json(SERVER* server, json_t* new_json) return rval; } -const char* object_relation_types[] = +static bool is_valid_relationship_body(json_t* json) { - MXS_JSON_PTR_RELATIONSHIPS_SERVERS, - NULL -}; + bool rval = true; + + json_t* obj = mxs_json_pointer(json, MXS_JSON_PTR_DATA); + + if (!obj) + { + runtime_error("Field '%s' is not defined", MXS_JSON_PTR_DATA); + rval = false; + } + else if (!json_is_array(obj)) + { + runtime_error("Field '%s' is not an array", MXS_JSON_PTR_DATA); + rval = false; + } + + return rval; +} + +bool runtime_alter_server_relationships_from_json(SERVER* server, const char* type, json_t* json) +{ + bool rval = false; + mxs::Closer old_json(server_to_json(server, "")); + ss_dassert(old_json.get()); + + if (is_valid_relationship_body(json)) + { + mxs::Closer j(json_pack("{s: {s: {s: {s: O}}}}", "data", + "relationships", type, "data", + json_object_get(json, "data"))); + + if (server_to_object_relations(server, old_json.get(), j.get())) + { + rval = true; + } + } + + return rval; +} static bool object_relation_is_valid(const std::string& type, const std::string& value) { @@ -1624,6 +1659,48 @@ bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json) return rval; } +bool runtime_alter_monitor_relationships_from_json(MXS_MONITOR* monitor, json_t* json) +{ + bool rval = false; + mxs::Closer old_json(monitor_to_json(monitor, "")); + ss_dassert(old_json.get()); + + if (is_valid_relationship_body(json)) + { + mxs::Closer j(json_pack("{s: {s: {s: {s: O}}}}", "data", + "relationships", "servers", "data", + json_object_get(json, "data"))); + + if (object_to_server_relations(monitor->name, old_json.get(), j.get())) + { + rval = true; + } + } + + return rval; +} + +bool runtime_alter_service_relationships_from_json(SERVICE* service, json_t* json) +{ + bool rval = false; + mxs::Closer old_json(service_to_json(service, "")); + ss_dassert(old_json.get()); + + if (is_valid_relationship_body(json)) + { + mxs::Closer j(json_pack("{s: {s: {s: {s: O}}}}", "data", + "relationships", "servers", "data", + json_object_get(json, "data"))); + + if (object_to_server_relations(service->name, old_json.get(), j.get())) + { + rval = true; + } + } + + return rval; +} + /** * @brief Check if the service parameter can be altered at runtime * diff --git a/server/core/maxscale/config_runtime.h b/server/core/maxscale/config_runtime.h index 5014bddc0..60297f8c6 100644 --- a/server/core/maxscale/config_runtime.h +++ b/server/core/maxscale/config_runtime.h @@ -220,6 +220,17 @@ SERVER* runtime_create_server_from_json(json_t* json); */ bool runtime_alter_server_from_json(SERVER* server, json_t* new_json); +/** + * @brief Alter server relationships + * + * @param server Server to alter + * @param type Type of the relation, either @c services or @c monitors + * @param json JSON that defines the relationship data + * + * @return True if the relationships were successfully modified + */ +bool runtime_alter_server_relationships_from_json(SERVER* server, const char* type, json_t* json); + /** * @brief Create a new monitor from JSON * @@ -239,6 +250,16 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json); */ bool runtime_alter_monitor_from_json(MXS_MONITOR* monitor, json_t* new_json); +/** + * @brief Alter monitor relationships + * + * @param monitor Monitor to alter + * @param json JSON that defines the new relationships + * + * @return True if the relationships were successfully modified + */ +bool runtime_alter_monitor_relationships_from_json(MXS_MONITOR* monitor, json_t* json); + /** * @brief Alter a service using JSON * @@ -249,6 +270,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 Alter service relationships + * + * @param service Service to alter + * @param json JSON that defines the new relationships + * + * @return True if the relationships were successfully modified + */ +bool runtime_alter_service_relationships_from_json(SERVICE* service, json_t* json); + /** * @brief Create a listener from JSON * diff --git a/server/core/resource.cc b/server/core/resource.cc index 25fc3d09d..fa1ccc36f 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -285,6 +285,29 @@ HttpResponse cb_alter_server(const HttpRequest& request) return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } +HttpResponse do_alter_server_relationship(const HttpRequest& request, const char* type) +{ + SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str()); + ss_dassert(server && request.get_json()); + + if (runtime_alter_server_relationships_from_json(server, type, request.get_json())) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); +} + +HttpResponse cb_alter_server_service_relationship(const HttpRequest& request) +{ + return do_alter_server_relationship(request, "services"); +} + +HttpResponse cb_alter_server_monitor_relationship(const HttpRequest& request) +{ + return do_alter_server_relationship(request, "monitors"); +} + HttpResponse cb_create_monitor(const HttpRequest& request) { ss_dassert(request.get_json()); @@ -323,6 +346,19 @@ HttpResponse cb_alter_monitor(const HttpRequest& request) return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } +HttpResponse cb_alter_monitor_server_relationship(const HttpRequest& request) +{ + MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str()); + ss_dassert(monitor && request.get_json()); + + if (runtime_alter_monitor_relationships_from_json(monitor, request.get_json())) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); +} + HttpResponse cb_alter_service(const HttpRequest& request) { SERVICE* service = service_find(request.uri_part(1).c_str()); @@ -336,6 +372,19 @@ HttpResponse cb_alter_service(const HttpRequest& request) return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); } +HttpResponse cb_alter_service_server_relationship(const HttpRequest& request) +{ + SERVICE* service = service_find(request.uri_part(1).c_str()); + ss_dassert(service && request.get_json()); + + if (runtime_alter_service_relationships_from_json(service, request.get_json())) + { + return HttpResponse(MHD_HTTP_NO_CONTENT); + } + + return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error()); +} + HttpResponse cb_alter_logs(const HttpRequest& request) { ss_dassert(request.get_json()); @@ -792,6 +841,16 @@ public: m_patch.push_back(SResource(new Resource(cb_alter_logs, 2, "maxscale", "logs"))); m_patch.push_back(SResource(new Resource(cb_alter_maxscale, 1, "maxscale"))); + /** Update resource relationships directly */ + m_patch.push_back(SResource(new Resource(cb_alter_server_service_relationship, 4, + "servers", ":server", "relationships", "services"))); + m_patch.push_back(SResource(new Resource(cb_alter_server_monitor_relationship, 4, + "servers", ":server", "relationships", "monitors"))); + m_patch.push_back(SResource(new Resource(cb_alter_monitor_server_relationship, 4, + "monitors", ":monitor", "relationships", "servers"))); + m_patch.push_back(SResource(new Resource(cb_alter_service_server_relationship, 4, + "services", ":service", "relationships", "servers"))); + /** All patch resources require a request body */ for (ResourceList::iterator it = m_patch.begin(); it != m_patch.end(); it++) {