MaxScale/server/core/resource.cc
Markus Mäkelä f54038f378 Process module command output even on errors
The output generated by a failed call to a module command was previously
overwritten with the error messages stored in the module command
subsystem.

In the case of a failure, the proper procedure is to check if the output
generated by the module command conforms to the JSON API error
specification and if it does, combine it with any system generated error
messages. If the output does not conform, it is stored in the "meta" field
of the returned object. This allows all of the generated output to be
saved.
2018-01-10 15:08:06 +02:00

1185 lines
36 KiB
C++

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2020-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "internal/resource.hh"
#include <list>
#include <sstream>
#include <map>
#include <maxscale/alloc.h>
#include <maxscale/jansson.hh>
#include <maxscale/spinlock.hh>
#include <maxscale/json_api.h>
#include <maxscale/housekeeper.h>
#include <maxscale/http.hh>
#include <maxscale/adminusers.h>
#include <maxscale/modulecmd.h>
#include <maxscale/semaphore.hh>
#include "internal/httprequest.hh"
#include "internal/httpresponse.hh"
#include "internal/session.h"
#include "internal/filter.h"
#include "internal/monitor.h"
#include "internal/service.h"
#include "internal/config_runtime.h"
#include "internal/modules.h"
#include "internal/worker.h"
#include "internal/worker.hh"
using std::list;
using std::map;
using std::string;
using std::stringstream;
using mxs::SpinLock;
using mxs::SpinLockGuard;
Resource::Resource(ResourceCallback cb, int components, ...) :
m_cb(cb),
m_is_glob(false),
m_constraints(NONE)
{
va_list args;
va_start(args, components);
for (int i = 0; i < components; i++)
{
string part = va_arg(args, const char*);
m_path.push_back(part);
if (part == "?")
{
m_is_glob = true;
}
}
va_end(args);
}
Resource::~Resource()
{
}
bool Resource::match(const HttpRequest& request) const
{
bool rval = false;
if (request.uri_part_count() == m_path.size() || m_is_glob)
{
rval = true;
size_t parts = MXS_MIN(request.uri_part_count(), m_path.size());
for (size_t i = 0; i < parts; i++)
{
if (m_path[i] != request.uri_part(i) &&
!matching_variable_path(m_path[i], request.uri_part(i)))
{
rval = false;
break;
}
}
}
return rval;
}
HttpResponse Resource::call(const HttpRequest& request) const
{
return m_cb(request);
};
bool Resource::matching_variable_path(const string& path, const string& target) const
{
bool rval = false;
if (path[0] == ':')
{
if ((path == ":service" && service_find(target.c_str())) ||
(path == ":server" && server_find_by_unique_name(target.c_str())) ||
(path == ":filter" && filter_def_find(target.c_str())) ||
(path == ":monitor" && monitor_find(target.c_str())) ||
(path == ":module" && get_module(target.c_str(), NULL)) ||
(path == ":inetuser" && admin_inet_user_exists(target.c_str())) ||
(path == ":unixuser" && admin_linux_account_enabled(target.c_str())))
{
rval = true;
}
else if (path == ":session")
{
size_t id = atoi(target.c_str());
MXS_SESSION* ses = session_get_by_id(id);
if (ses)
{
session_put_ref(ses);
rval = true;
}
}
else if (path == ":thread")
{
char* end;
int id = strtol(target.c_str(), &end, 10);
if (*end == '\0' && mxs_worker_get(id))
{
rval = true;
}
}
}
else if (path == "?")
{
/** Wildcard match */
rval = true;
}
return rval;
}
void Resource::add_constraint(resource_constraint type)
{
m_constraints |= static_cast<uint32_t>(type);
}
bool Resource::requires_body() const
{
return m_constraints & REQUIRE_BODY;
}
namespace
{
static bool drop_path_part(std::string& path)
{
size_t pos = path.find_last_of('/');
bool rval = false;
if (pos != std::string::npos)
{
path.erase(pos);
rval = true;
}
return rval && path.length();
}
/**
* Class that keeps track of resource modification times
*/
class ResourceWatcher
{
public:
ResourceWatcher() :
m_init(time(NULL))
{
}
void modify(const std::string& orig_path)
{
std::string path = orig_path;
do
{
map<std::string, uint64_t>::iterator it = m_etag.find(path);
if (it != m_etag.end())
{
it->second++;
}
else
{
// First modification
m_etag[path] = 1;
}
m_last_modified[path] = time(NULL);
}
while (drop_path_part(path));
}
time_t last_modified(const string& path) const
{
map<string, time_t>::const_iterator it = m_last_modified.find(path);
if (it != m_last_modified.end())
{
return it->second;
}
// Resource has not yet been updated
return m_init;
}
uint64_t etag(const string& path) const
{
map<string, uint64_t>::const_iterator it = m_etag.find(path);
if (it != m_etag.end())
{
return it->second;
}
// Resource has not yet been updated
return 0;
}
private:
time_t m_init;
map<string, time_t> m_last_modified;
map<string, uint64_t> m_etag;
};
HttpResponse cb_stop_monitor(const HttpRequest& request)
{
MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str());
monitorStop(monitor);
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
HttpResponse cb_start_monitor(const HttpRequest& request)
{
MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str());
monitorStart(monitor, monitor->parameters);
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
HttpResponse cb_stop_service(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
serviceStop(service);
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
HttpResponse cb_start_service(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
serviceStart(service);
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
HttpResponse cb_create_server(const HttpRequest& request)
{
ss_dassert(request.get_json());
if (runtime_create_server_from_json(request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_alter_server(const HttpRequest& request)
{
SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str());
ss_dassert(server && request.get_json());
if (runtime_alter_server_from_json(server, request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
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());
if (runtime_create_monitor_from_json(request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_create_service_listener(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
ss_dassert(service && request.get_json());
if (runtime_create_listener_from_json(service, request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_alter_monitor(const HttpRequest& request)
{
MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str());
ss_dassert(monitor && request.get_json());
if (runtime_alter_monitor_from_json(monitor, request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
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());
ss_dassert(service && request.get_json());
if (runtime_alter_service_from_json(service, request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
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());
if (runtime_alter_logs_from_json(request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_delete_server(const HttpRequest& request)
{
SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str());
ss_dassert(server);
if (runtime_destroy_server(server))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_delete_monitor(const HttpRequest& request)
{
MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str());
ss_dassert(monitor);
if (runtime_destroy_monitor(monitor))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_delete_listener(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
ss_dassert(service);
std::string listener = request.uri_part(3);
if (!service_has_named_listener(service, listener.c_str()))
{
return HttpResponse(MHD_HTTP_NOT_FOUND);
}
else if (!runtime_destroy_listener(service, listener.c_str()))
{
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
HttpResponse cb_all_servers(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, server_list_to_json(request.host()));
}
HttpResponse cb_get_server(const HttpRequest& request)
{
SERVER* server = server_find_by_unique_name(request.uri_part(1).c_str());
ss_dassert(server);
return HttpResponse(MHD_HTTP_OK, server_to_json(server, request.host()));
}
HttpResponse cb_all_services(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, service_list_to_json(request.host()));
}
HttpResponse cb_get_service(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
ss_dassert(service);
return HttpResponse(MHD_HTTP_OK, service_to_json(service, request.host()));
}
HttpResponse cb_get_all_service_listeners(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
return HttpResponse(MHD_HTTP_OK, service_listener_list_to_json(service, request.host()));
}
HttpResponse cb_get_service_listener(const HttpRequest& request)
{
SERVICE* service = service_find(request.uri_part(1).c_str());
std::string listener = request.uri_part(3);
ss_dassert(service);
if (!service_has_named_listener(service, listener.c_str()))
{
return HttpResponse(MHD_HTTP_NOT_FOUND);
}
return HttpResponse(MHD_HTTP_OK,
service_listener_to_json(service, listener.c_str(),
request.host()));
}
HttpResponse cb_all_filters(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, filter_list_to_json(request.host()));
}
HttpResponse cb_get_filter(const HttpRequest& request)
{
MXS_FILTER_DEF* filter = filter_def_find(request.uri_part(1).c_str());
ss_dassert(filter);
return HttpResponse(MHD_HTTP_OK, filter_to_json(filter, request.host()));
}
HttpResponse cb_all_monitors(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, monitor_list_to_json(request.host()));
}
HttpResponse cb_get_monitor(const HttpRequest& request)
{
MXS_MONITOR* monitor = monitor_find(request.uri_part(1).c_str());
ss_dassert(monitor);
return HttpResponse(MHD_HTTP_OK, monitor_to_json(monitor, request.host()));
}
HttpResponse cb_all_sessions(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, session_list_to_json(request.host()));
}
HttpResponse cb_get_session(const HttpRequest& request)
{
int id = atoi(request.uri_part(1).c_str());
MXS_SESSION* session = session_get_by_id(id);
if (session)
{
json_t* json = session_to_json(session, request.host());
session_put_ref(session);
return HttpResponse(MHD_HTTP_OK, json);
}
return HttpResponse(MHD_HTTP_NOT_FOUND);
}
HttpResponse cb_maxscale(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, config_maxscale_to_json(request.host()));
}
HttpResponse cb_alter_maxscale(const HttpRequest& request)
{
ss_dassert(request.get_json());
if (runtime_alter_maxscale_from_json(request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_logs(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, mxs_logs_to_json(request.host()));
}
HttpResponse cb_flush(const HttpRequest& request)
{
int code = MHD_HTTP_INTERNAL_SERVER_ERROR;
// Flush logs
if (mxs_log_rotate() == 0)
{
code = MHD_HTTP_NO_CONTENT;
}
return HttpResponse(code);
}
HttpResponse cb_all_threads(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, mxs_worker_list_to_json(request.host()));
}
HttpResponse cb_thread(const HttpRequest& request)
{
int id = atoi(request.last_uri_part().c_str());
return HttpResponse(MHD_HTTP_OK, mxs_worker_to_json(request.host(), id));
}
HttpResponse cb_tasks(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, hk_tasks_json(request.host()));
}
HttpResponse cb_all_modules(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, module_list_to_json(request.host()));
}
HttpResponse cb_module(const HttpRequest& request)
{
const MXS_MODULE* module = get_module(request.last_uri_part().c_str(), NULL);
return HttpResponse(MHD_HTTP_OK, module_to_json(module, request.host()));
}
HttpResponse cb_all_users(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_ALL));
}
HttpResponse cb_all_inet_users(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_INET));
}
HttpResponse cb_all_unix_users(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK, admin_all_users_to_json(request.host(), USER_TYPE_UNIX));
}
HttpResponse cb_inet_user(const HttpRequest& request)
{
string user = request.uri_part(2);
return HttpResponse(MHD_HTTP_OK, admin_user_to_json(request.host(), user.c_str(), USER_TYPE_INET));
}
HttpResponse cb_unix_user(const HttpRequest& request)
{
string user = request.uri_part(2);
return HttpResponse(MHD_HTTP_OK, admin_user_to_json(request.host(), user.c_str(), USER_TYPE_UNIX));
}
HttpResponse cb_create_user(const HttpRequest& request)
{
ss_dassert(request.get_json());
if (runtime_create_user_from_json(request.get_json()))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_delete_user(const HttpRequest& request)
{
string user = request.last_uri_part();
string type = request.uri_part(1);
if ((type == CN_INET && runtime_remove_user(user.c_str(), USER_TYPE_INET)) ||
(type == CN_UNIX && runtime_remove_user(user.c_str(), USER_TYPE_UNIX)))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
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(CN_STATE).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 `%s` "
"parameter", CN_STATE));
}
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(CN_STATE).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 `%s` "
"parameter", CN_STATE));
}
HttpResponse cb_modulecmd(const HttpRequest& request)
{
std::string module = request.uri_part(2);
std::string identifier = request.uri_segment(3, request.uri_part_count());
std::string verb = request.get_verb();
const MODULECMD* cmd = modulecmd_find_command(module.c_str(), identifier.c_str());
if (cmd)
{
if ((!MODULECMD_MODIFIES_DATA(cmd) && verb == MHD_HTTP_METHOD_GET) ||
(MODULECMD_MODIFIES_DATA(cmd) && verb == MHD_HTTP_METHOD_POST))
{
int n_opts = (int)request.get_option_count();
char* opts[n_opts];
request.copy_options(opts);
MODULECMD_ARG* args = modulecmd_arg_parse(cmd, n_opts, (const void**)opts);
bool rval = false;
json_t* output = NULL;
if (args)
{
rval = modulecmd_call_command(cmd, args, &output);
}
for (int i = 0; i < n_opts; i++)
{
MXS_FREE(opts[i]);
}
int rc;
if (output && json_object_get(output, "errors") == NULL)
{
/**
* Store the command output in the meta field. This allows
* all the commands to conform to the JSON API even though
* the content of the field can vary from command to command.
*
* If the output is an JSON API error, we don't do anything to it
*/
std::string self = "/"; // The uri_segment doesn't have the leading slash
self += request.uri_segment(0, request.uri_part_count());
output = mxs_json_metadata(request.host(), self.c_str(), output);
}
if (rval)
{
rc = output ? MHD_HTTP_OK : MHD_HTTP_NO_CONTENT;
}
else
{
rc = MHD_HTTP_FORBIDDEN;
json_t* err = modulecmd_get_json_error();
if (err)
{
if (!output)
{
// No output, only errors
output = err;
}
else
{
// Both output and errors
json_t* output_err = json_object_get(output, "errors");
if (output_err)
{
// The output already contains an error array and append to it
json_array_append(output_err, json_object_get(err, "errors"));
}
else
{
// No error, we can just assign the error array
json_object_set(output, "errors", json_object_get(err, "errors"));
}
json_decref(err);
}
}
}
return HttpResponse(rc, output);
}
}
return HttpResponse(MHD_HTTP_NOT_FOUND);
}
HttpResponse cb_send_ok(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK);
}
class RootResource
{
RootResource(const RootResource&);
RootResource& operator=(const RootResource&);
public:
typedef std::shared_ptr<Resource> SResource;
typedef list<SResource> ResourceList;
/**
* Create REST API resources
*
* Each resource represents either a collection of resources, an individual
* resource, a sub-resource of a resource or an "action" endpoint which
* executes an action.
*
* The resources are defined by the Resource class. Each resource maps to a
* HTTP method and one or more paths. The path components can contain either
* an explicit string, a colon-prefixed object type or a question mark for
* a path component that matches everything.
*/
RootResource()
{
// Special resources required by OPTION etc.
m_get.push_back(SResource(new Resource(cb_send_ok, 0)));
m_get.push_back(SResource(new Resource(cb_send_ok, 1, "*")));
m_get.push_back(SResource(new Resource(cb_all_servers, 1, "servers")));
m_get.push_back(SResource(new Resource(cb_get_server, 2, "servers", ":server")));
m_get.push_back(SResource(new Resource(cb_all_services, 1, "services")));
m_get.push_back(SResource(new Resource(cb_get_service, 2, "services", ":service")));
m_get.push_back(SResource(new Resource(cb_get_all_service_listeners, 3,
"services", ":service", "listeners")));
m_get.push_back(SResource(new Resource(cb_get_service_listener, 4,
"services", ":service", "listeners", "?")));
m_get.push_back(SResource(new Resource(cb_all_filters, 1, "filters")));
m_get.push_back(SResource(new Resource(cb_get_filter, 2, "filters", ":filter")));
m_get.push_back(SResource(new Resource(cb_all_monitors, 1, "monitors")));
m_get.push_back(SResource(new Resource(cb_get_monitor, 2, "monitors", ":monitor")));
m_get.push_back(SResource(new Resource(cb_all_sessions, 1, "sessions")));
m_get.push_back(SResource(new Resource(cb_get_session, 2, "sessions", ":session")));
m_get.push_back(SResource(new Resource(cb_maxscale, 1, "maxscale")));
m_get.push_back(SResource(new Resource(cb_all_threads, 2, "maxscale", "threads")));
m_get.push_back(SResource(new Resource(cb_thread, 3, "maxscale", "threads", ":thread")));
m_get.push_back(SResource(new Resource(cb_logs, 2, "maxscale", "logs")));
m_get.push_back(SResource(new Resource(cb_tasks, 2, "maxscale", "tasks")));
m_get.push_back(SResource(new Resource(cb_all_modules, 2, "maxscale", "modules")));
m_get.push_back(SResource(new Resource(cb_module, 3, "maxscale", "modules", ":module")));
/** For all read-only module commands */
m_get.push_back(SResource(new Resource(cb_modulecmd, 4, "maxscale", "modules", ":module", "?")));
m_get.push_back(SResource(new Resource(cb_all_users, 1, "users")));
m_get.push_back(SResource(new Resource(cb_all_inet_users, 2, "users", "inet")));
m_get.push_back(SResource(new Resource(cb_all_unix_users, 2, "users", "unix")));
m_get.push_back(SResource(new Resource(cb_inet_user, 3, "users", "inet", ":inetuser")));
m_get.push_back(SResource(new Resource(cb_unix_user, 3, "users", "unix", ":unixuser")));
/** Create new resources */
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")));
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")));
/** All of the above require a request body */
for (ResourceList::iterator it = m_post.begin(); it != m_post.end(); it++)
{
SResource& r = *it;
r->add_constraint(Resource::REQUIRE_BODY);
}
/**
* NOTE: all POST resources added after this DO NOT require a request body.
*/
/** For all module commands that modify state/data */
m_post.push_back(SResource(new Resource(cb_modulecmd, 4, "maxscale", "modules", ":module", "?")));
m_post.push_back(SResource(new Resource(cb_flush, 3, "maxscale", "logs", "flush")));
/** Update resources */
m_patch.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server")));
m_patch.push_back(SResource(new Resource(cb_alter_monitor, 2, "monitors", ":monitor")));
m_patch.push_back(SResource(new Resource(cb_alter_service, 2, "services", ":service")));
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++)
{
SResource& r = *it;
r->add_constraint(Resource::REQUIRE_BODY);
}
/**
* NOTE: all PATCH resources added after this DO NOT require a request body.
*/
/** Change resource states */
m_put.push_back(SResource(new Resource(cb_stop_monitor, 3, "monitors", ":monitor", "stop")));
m_put.push_back(SResource(new Resource(cb_start_monitor, 3, "monitors", ":monitor", "start")));
m_put.push_back(SResource(new Resource(cb_stop_service, 3, "services", ":service", "stop")));
m_put.push_back(SResource(new Resource(cb_start_service, 3, "services", ":service", "start")));
m_put.push_back(SResource(new Resource(cb_set_server, 3, "servers", ":server", "set")));
m_put.push_back(SResource(new Resource(cb_clear_server, 3, "servers", ":server", "clear")));
m_delete.push_back(SResource(new Resource(cb_delete_server, 2, "servers", ":server")));
m_delete.push_back(SResource(new Resource(cb_delete_monitor, 2, "monitors", ":monitor")));
m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "inet", ":inetuser")));
m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "unix", ":unixuser")));
/** The wildcard for listener name isn't a good solution as it adds
* a burden to the callback and requires it to do the checking but it'll
* do for the time being */
m_delete.push_back(SResource(new Resource(cb_delete_listener, 4,
"services", ":service", "listeners", "?")));
}
~RootResource()
{
}
ResourceList::const_iterator find_resource(const ResourceList& list, const HttpRequest& request) const
{
for (ResourceList::const_iterator it = list.begin(); it != list.end(); it++)
{
Resource& r = *(*it);
if (r.match(request))
{
return it;
}
}
return list.end();
}
HttpResponse process_request_type(const ResourceList& list, const HttpRequest& request)
{
ResourceList::const_iterator it = find_resource(list, request);
if (it != list.end())
{
Resource& r = *(*it);
if (r.requires_body() && request.get_json() == NULL)
{
return HttpResponse(MHD_HTTP_FORBIDDEN, mxs_json_error("Missing request body"));
}
return r.call(request);
}
return HttpResponse(MHD_HTTP_NOT_FOUND);
}
string get_supported_methods(const HttpRequest& request)
{
list<string> l;
if (find_resource(m_get, request) != m_get.end())
{
l.push_back(MHD_HTTP_METHOD_GET);
}
if (find_resource(m_put, request) != m_put.end())
{
l.push_back(MHD_HTTP_METHOD_PUT);
}
if (find_resource(m_post, request) != m_post.end())
{
l.push_back(MHD_HTTP_METHOD_POST);
}
if (find_resource(m_delete, request) != m_delete.end())
{
l.push_back(MHD_HTTP_METHOD_DELETE);
}
stringstream rval;
if (l.size() > 0)
{
rval << l.front();
l.pop_front();
}
for (list<string>::iterator it = l.begin(); it != l.end(); it++)
{
rval << ", " << *it;
}
return rval.str();
}
HttpResponse process_request(const HttpRequest& request)
{
if (request.get_verb() == MHD_HTTP_METHOD_GET)
{
return process_request_type(m_get, request);
}
else if (request.get_verb() == MHD_HTTP_METHOD_PUT)
{
return process_request_type(m_put, request);
}
else if (request.get_verb() == MHD_HTTP_METHOD_PATCH)
{
return process_request_type(m_patch, request);
}
else if (request.get_verb() == MHD_HTTP_METHOD_POST)
{
return process_request_type(m_post, request);
}
else if (request.get_verb() == MHD_HTTP_METHOD_DELETE)
{
return process_request_type(m_delete, request);
}
else if (request.get_verb() == MHD_HTTP_METHOD_OPTIONS)
{
string methods = get_supported_methods(request);
if (methods.size() > 0)
{
HttpResponse response(MHD_HTTP_OK);
response.add_header(HTTP_RESPONSE_HEADER_ACCEPT, methods);
return response;
}
}
else if (request.get_verb() == MHD_HTTP_METHOD_HEAD)
{
/** Do a GET and just drop the body of the response */
HttpResponse response = process_request_type(m_get, request);
response.drop_response();
return response;
}
return HttpResponse(MHD_HTTP_METHOD_NOT_ALLOWED);
}
private:
ResourceList m_get; /**< GET request handlers */
ResourceList m_put; /**< PUT request handlers */
ResourceList m_post; /**< POST request handlers */
ResourceList m_delete; /**< DELETE request handlers */
ResourceList m_patch; /**< PATCH request handlers */
};
static RootResource resources; /**< Core resource set */
static ResourceWatcher watcher; /**< Modification watcher */
static SpinLock resource_lock;
static bool request_modifies_data(const string& verb)
{
return verb == MHD_HTTP_METHOD_POST ||
verb == MHD_HTTP_METHOD_PUT ||
verb == MHD_HTTP_METHOD_DELETE ||
verb == MHD_HTTP_METHOD_PATCH;
}
static bool request_reads_data(const string& verb)
{
return verb == MHD_HTTP_METHOD_GET ||
verb == MHD_HTTP_METHOD_HEAD;
}
static bool request_precondition_met(const HttpRequest& request, HttpResponse& response)
{
bool rval = true;
string str;
const string& uri = request.get_uri();
if ((str = request.get_header(MHD_HTTP_HEADER_IF_MODIFIED_SINCE)).length())
{
if (watcher.last_modified(uri) <= http_from_date(str))
{
rval = false;
response = HttpResponse(MHD_HTTP_NOT_MODIFIED);
}
}
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE)).length())
{
if (watcher.last_modified(uri) > http_from_date(str))
{
rval = false;
response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED);
}
}
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_MATCH)).length())
{
str = str.substr(1, str.length() - 2);
if (watcher.etag(uri) != strtoul(str.c_str(), NULL, 10))
{
rval = false;
response = HttpResponse(MHD_HTTP_PRECONDITION_FAILED);
}
}
else if ((str = request.get_header(MHD_HTTP_HEADER_IF_NONE_MATCH)).length())
{
str = str.substr(1, str.length() - 2);
if (watcher.etag(uri) == strtoul(str.c_str(), NULL, 10))
{
rval = false;
response = HttpResponse(MHD_HTTP_NOT_MODIFIED);
}
}
return rval;
}
static HttpResponse handle_request(const HttpRequest& request)
{
MXS_DEBUG("%s %s %s", request.get_verb().c_str(), request.get_uri().c_str(),
request.get_json_str().c_str());
HttpResponse rval;
if (request_precondition_met(request, rval))
{
rval = resources.process_request(request);
if (request_modifies_data(request.get_verb()))
{
switch (rval.get_code())
{
case MHD_HTTP_OK:
case MHD_HTTP_NO_CONTENT:
case MHD_HTTP_CREATED:
watcher.modify(request.get_uri());
break;
default:
break;
}
}
else if (request_reads_data(request.get_verb()))
{
const string& uri = request.get_uri();
rval.add_header(HTTP_RESPONSE_HEADER_LAST_MODIFIED,
http_to_date(watcher.last_modified(uri)));
stringstream ss;
ss << "\"" << watcher.etag(uri) << "\"";
rval.add_header(HTTP_RESPONSE_HEADER_ETAG, ss.str());
}
}
return rval;
}
class ResourceTask: public mxs::Worker::Task
{
public:
ResourceTask(const HttpRequest& request):
m_request(request)
{
}
void execute(mxs::Worker& worker)
{
m_response = handle_request(m_request);
}
HttpResponse result()
{
return m_response;
}
private:
const HttpRequest& m_request;
HttpResponse m_response;
};
}
HttpResponse resource_handle_request(const HttpRequest& request)
{
mxs::Worker* worker = mxs::Worker::get(0);
mxs::Semaphore sem;
ResourceTask task(request);
worker->post(&task, &sem);
sem.wait();
return task.result();
}