From 42b3f970c59ad77454568f68b9af950f8b6e9c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 4 Mar 2019 16:40:17 +0200 Subject: [PATCH] MXS-2260: Add REST API tutorial The tutorial shows how to configure the MaxScale REST API and communicate with it using the `curl` command line client. --- Documentation/Documentation-Contents.md | 1 + Documentation/Tutorials/REST-API-Tutorial.md | 355 +++++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 Documentation/Tutorials/REST-API-Tutorial.md diff --git a/Documentation/Documentation-Contents.md b/Documentation/Documentation-Contents.md index ce733ee30..d14b34f5b 100644 --- a/Documentation/Documentation-Contents.md +++ b/Documentation/Documentation-Contents.md @@ -53,6 +53,7 @@ Here are tutorials on monitoring and managing MariaDB MaxScale in cluster enviro - [MariaDB MaxScale HA with Lsyncd](Tutorials/MaxScale-HA-with-lsyncd.md) - [Nagios Plugins for MariaDB MaxScale Tutorial](Tutorials/Nagios-Plugins.md) + - [REST API Tutorial](Tutorials/REST-API-Tutorial.md) ## Routers diff --git a/Documentation/Tutorials/REST-API-Tutorial.md b/Documentation/Tutorials/REST-API-Tutorial.md new file mode 100644 index 000000000..397b42f77 --- /dev/null +++ b/Documentation/Tutorials/REST-API-Tutorial.md @@ -0,0 +1,355 @@ +# REST API Tutorial + +This tutorial is a quick overview of what the MaxScale REST API offers, how it +can be used to inspect the state of MaxScale and how to use it to modify the +runtime configuration of MaxScale. The tutorial uses the `curl` command line +client to demonstrate how the API is used. + +## Configuration and Hardening + +The MaxScale REST API listens on port 8989 on the local host. The `admin_port` +and `admin_host` parameters control which port and address the REST API listens +on. Note that for security reasons the API only listens for local connections +with the default configuration. It is critical that the default credentials are +changed and TLS/SSL encryption is configured before exposing the REST API to a +network. + +The default user for the REST API is `admin` and the password is `mariadb`. The +easiest way to secure the REST API is to use the `maxctrl` command line client +to create a new admin user and delete the default one. To do this, run the +following commands: + +``` +maxctrl create user my_user my_password --type=admin +maxctrl destroy user admin +``` + +This will create the user `my_user` with the password `my_password` that is an +administrative account. After this account is created, the default `admin` +account is removed with the next command. + +The next step is to enable TLS encryption. To do this, you need a CA +certificate, a private key and a public certificate file all in PEM format. Add +the following three parameters under the `[maxscale]` section of the MaxScale +configuration file and restart MaxScale. + +``` +admin_ssl_key=/certs/server-key.pem +admin_ssl_cert=/certs/server-cert.pem +admin_ssl_ca_cert=/certs/ca-cert.pem +``` + +Use `maxctrl` to verify that the TLS encryption is enabled. In this tutorial our +server certificates are self-signed so the `--tls-verify-server-cert=false` +option is required. + +``` +maxctrl --user=my_user --password=my_password --secure --tls-ca-cert=/certs/ca-cert.pem --tls-verify-server-cert=false show maxscale +``` + +If no errors are raised, this means that the communication via the REST API is +now secure and can be used across networks. + +## Requesting Data + +**Note:** For the sake of brevity, the rest of this tutorial will omit the +TLS/SSL options for the `curl` command line. For more information, refer to the +`curl` manpage. + +The most basic task to do with the REST API is to see whether MaxScale is up and +running. To do this, we do a HTTP request on the root resource (the `-i` option +shows the HTTP headers). + +`curl -i 127.0.0.1:8989/v1/` +``` +HTTP/1.1 200 OK +Connection: Keep-Alive +Content-Length: 0 +Last-Modified: Mon, 04 Mar 2019 08:23:09 GMT +ETag: "0" +Date: Mon, 04 Mar 19 08:29:41 GMT +``` + +To query a resource collection endpoint, append it to the URL. The `/v1/filters/` +endpoint shows the list of filters configured in MaxScale. This is a _resource +collection_ endpoint: it contains the list of all resources of a particular +type. + +`curl 127.0.0.1:8989/v1/filters` +``` +{ + "links": { + "self": "http://127.0.0.1:8989/v1/filters/" + }, + "data": [ + { + "id": "Hint", + "type": "filters", + "relationships": { + "services": { + "links": { + "self": "http://127.0.0.1:8989/v1/services/" + }, + "data": [ + { + "id": "RW-Split-Hint-Router", + "type": "services" + } + ] + } + }, + "attributes": { + "module": "hintfilter", + "parameters": {} + }, + "links": { + "self": "http://127.0.0.1:8989/v1/filters/Hint" + } + }, + { + "id": "Logger", + "type": "filters", + "relationships": { + "services": { + "links": { + "self": "http://127.0.0.1:8989/v1/services/" + }, + "data": [] + } + }, + "attributes": { + "module": "qlafilter", + "parameters": { + "match": null, + "exclude": null, + "user": null, + "source": null, + "filebase": "/tmp/log", + "options": "ignorecase", + "log_type": "session", + "log_data": "date,user,query", + "newline_replacement": "\" \"", + "separator": ",", + "flush": false, + "append": false + }, + "filter_diagnostics": { + "separator": ",", + "newline_replacement": "\" \"" + } + }, + "links": { + "self": "http://127.0.0.1:8989/v1/filters/Logger" + } + } + ] +} +``` + +The `data` holds the actual list of resources: the `Hint` and `Logger` +filters. Each object has the `id` field which is the unique name of that +object. It is the same as the section name in `maxscale.cnf`. + +Each resource in the list has a `relationships` object. This shows the +relationship links between resources. In our example, the `Hint` filter is used +by a service named `RW-Split-Hint-Router` and the `Logger` is not currently in +use. + +To request an individual resource, we add the object name to the resource +collection URL. For example, if we want to get only the `Logger` filter we +execute the following command. + +`curl 127.0.0.1:8989/v1/filters/Logger` +``` +{ + "links": { + "self": "http://127.0.0.1:8989/v1/filters/Logger" + }, + "data": { + "id": "Logger", + "type": "filters", + "relationships": { + "services": { + "links": { + "self": "http://127.0.0.1:8989/v1/services/" + }, + "data": [] + } + }, + "attributes": { + "module": "qlafilter", + "parameters": { + "match": null, + "exclude": null, + "user": null, + "source": null, + "filebase": "/tmp/log", + "options": "ignorecase", + "log_type": "session", + "log_data": "date,user,query", + "newline_replacement": "\" \"", + "separator": ",", + "flush": false, + "append": false + }, + "filter_diagnostics": { + "separator": ",", + "newline_replacement": "\" \"" + } + }, + "links": { + "self": "http://127.0.0.1:8989/v1/filters/Logger" + } + } +} +``` + +Note that this time the `data` member holds an object instead of an array of +objects. All other parts of the response are similar to what was shown in the +previous example. + +## Creating Objects + +One of the uses of the REST API is to create new objects in MaxScale at +runtime. This allows new servers, services, filters, monitor and listeners to be +created without restarting MaxScale. + +For example, to create a new server in MaxScale the JSON definition of a server +must be sent to the REST API at the `/v1/servers/` endpoint. The request body +defines the server name as well as the parameters for it. + +To create objects with `curl`, first write the JSON definition into a file. + +``` +{ + "data": { + "id": "server1", + "type": "servers", + "attributes": { + "parameters": { + "address": "127.0.0.1", + "port": 3003 + } + } + } +} +``` + +To send the data, use the following command. + +``` +curl -X POST -d @new_server.txt 127.0.0.1:8989/v1/servers +``` + +The `-d` option takes a file name prefixed with a `@` as an argument. Here we +have `@new_server.txt` which is the name of the file where the JSON definition +was stored. The `-X` option defines the HTTP verb to use and to create a new +object we must use the POST verb. + +To verify the data request the newly created object. + +``` +curl 127.0.0.1:8989/v1/servers/server1 +``` + +## Modifying Data + +The easiest way to modify an object is to first request it, store the result in +a file, edit it and then send the updated object back to the REST API. + +Let's say we want to modify the port that the server we created earlier listens +on. First we request the current object and store the result in a file. + +``` +curl 127.0.0.1:8989/v1/servers/server1 > server1.txt +``` + +After that we edit the file and change the port from 3003 to 3306. Next the +modified JSON object is sent to the REST API as a PATCH command. To do this, +execute the following command. + +``` +curl -X PATCH -d @server1.txt 127.0.0.1:8989/v1/servers/server1 +``` + +To verify that the data was updated correctly, request the updated created +object. + +``` +curl 127.0.0.1:8989/v1/servers/server1 +``` + +## Object Relationships + +To continue with our previous example, we add the updated server to a +service. To do this, the `relationships` object of the server must be modified +to include the service we want to add the server to. + +To define a relationship between a server and a service, the `data` member must +have the `relationships` field and it must contain an object with the `services` +field (some fields omitted for brevity). + +``` +{ + "data": { + "id": "server1", + "type": "servers", + "relationships": { + "services": { + "data": [ + { + "id": "RW-Split-Router", + "type": "services" + } + ] + } + }, + "attributes": ... + } +} +``` + +The `data.relationships.services.data` field contains a list of objects that +define the `id` and `type` fields. The id is the name of the object (a service +or a monitor for servers) and the type tells which type it is. Only `services` +type objects should be present in the `services` object. + +In our example we are linking the `server1` server to the `RW-Split-Router` +service. As was seen with the previous example, the easiest way to do this is to +store the result, edit it and then send it back with a HTTP PATCH. + +If we want to remove a server from _all_ services, we can set the +`relationships` field to `{}`. The REST API interprets this as an instruction +to remove the server from all services and monitors. This is useful if you want +to delete the server which can only be done if it has no relationships to other +objects. + +## Deleting Objects + +To delete an object, simply execute a HTTP DELETE request on the resource you +want to delete. For example, to delete the `server1` server, execute the +following command. + +``` +curl -X DELETE 127.0.0.1:8989/v1/servers/server1 +``` + +## Further Reading + +The full list of all available endpoints in MaxScale can be found in the +[REST API documentation](../REST-API/API.md). + +The `maxctrl` command line client is self-documenting and the `maxctrl help` +command is a good tool for exploring the various commands that are available in +it. The `maxctrl api get` command can be useful way to explore the REST API as +it provides a way to easily extract values out of the JSON data generated by the +REST API. + +There is a multitude of REST API clients readily available and most of them are +far more convenient to use than `curl`. We recommend investigating what you need +and how you intend to either integrate or use the MaxScale REST API. Most modern +languages either have a built-in HTTP library or there exists a de facto +standard library. + +The MaxScale REST API follows the JSON API specification and there exist +libraries that are built specifically for these sorts of APIs