From a0f905bbe932b236f786115994bb3ea74a69ba7b Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 24 Aug 2016 13:51:24 +0300 Subject: [PATCH 01/36] Make maxscaled upgrade friendlier Currently, if the address/port information of a maxscaled protocol listener is not updated to socket when MaxScale is upgraded to 2.0, maxscaled would not start, with the effect of a user loosing maxadmin after an upgrade. After this change, if address/port information is detected, a warning is logged and the default socket path is used. That way, maxadmin will still be usable after an upgrade, even if the address/port information is not updated. --- server/modules/protocol/maxscaled.c | 37 ++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index db10f977d..58d5b8518 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -349,7 +349,7 @@ static int maxscaled_close(DCB *dcb) */ static int maxscaled_listen(DCB *listener, char *config) { - char *socket_path; + char *socket_path = NULL; /* check for default UNIX socket */ if (strncmp(config, MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG, MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG_LEN) == 0) @@ -358,19 +358,28 @@ static int maxscaled_listen(DCB *listener, char *config) } else { - socket_path = config; + const char* colon = strchr(config, ':'); + ss_dassert(colon); + const char* port = colon + 1; + + if (*port == '0') + { + // It seems "socket=socket-path" has been specified. + + // The colon etc. will be stripped away in dcb_listen_create_socket_unix. + socket_path = config; + } + else + { + MXS_WARNING("The 'maxscaled' protocol can only be used with a domain socket, but " + "it seems to have been configured with a socket and port: %s. " + "Using the default socket path instead: %s. " + "Remove all 'port' and 'address' entries from maxscaled protocol " + "listeners and replace with 'socket=default' or 'socket=path-to-socket'.", + config, MAXADMIN_DEFAULT_SOCKET); + socket_path = MAXADMIN_DEFAULT_SOCKET; + } } - /* check for UNIX socket path*/ - if (strchr(socket_path,'/') == NULL) - { - MXS_ERROR("Failed to start listening on '%s' with 'MaxScale Admin' protocol," - " only UNIX domain sockets are supported. Remove all 'port' and 'address'" - " parameters from this listener and add 'socket=default' to enable UNIX domain sockets.", socket_path); - return -1; - } - else - { - return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1; - } + return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1; } From 67cfafb23c3722bb63394525b517f0f9d2eb4ab2 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 24 Aug 2016 14:26:00 +0300 Subject: [PATCH 02/36] Enhance MaxAdmin info in 2.0 Upgrading document --- .../Upgrading/Upgrading-To-MaxScale-2.0.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md index 136985011..a1ca08064 100644 --- a/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md @@ -21,6 +21,29 @@ When 2.0 has been installed, MaxAdmin can only be used by `root` and other users must be added anew. Please consult [MaxAdmin documentation](../Reference/MaxAdmin.md) for more details. +This change requires the _maxscaled_ protocol listener entry in the +MaxScale configuration file to be updated; address and port information +must be replaced with socket information. For instance, an entry like +``` +[MaxAdmin Listener] +type=listener +protocol=maxscaled +address=localhost +port=6603 +``` +should be updated to +``` +[MaxAdmin Listener] +type=listener +protocol=maxscaled +socket=default +``` +where `default` corresponds to `/tmp/maxadmin.sock`. + +Note that if this update is *not* made, maxscaled will log a warning +and use the default socket path. This behaviour may change in later +releases of MaxScale. + ## MySQL Monitor The MySQL Monitor now assigns the stale state to the master server by default. From 146fb50cdbaf9442484679d08dd3741ee49262c3 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 29 Aug 2016 14:08:55 +0300 Subject: [PATCH 03/36] Tune maxscaled error message --- server/modules/protocol/maxscaled.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index 58d5b8518..41ae11377 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -371,10 +371,10 @@ static int maxscaled_listen(DCB *listener, char *config) } else { - MXS_WARNING("The 'maxscaled' protocol can only be used with a domain socket, but " - "it seems to have been configured with a socket and port: %s. " + MXS_WARNING("The 'maxscaled' protocol can only be used with a Unix domain socket, but " + "it seems to have been configured with an address and port: %s. " "Using the default socket path instead: %s. " - "Remove all 'port' and 'address' entries from maxscaled protocol " + "Remove all 'address' and 'port' entries from maxscaled protocol " "listeners and replace with 'socket=default' or 'socket=path-to-socket'.", config, MAXADMIN_DEFAULT_SOCKET); socket_path = MAXADMIN_DEFAULT_SOCKET; From 248a58629bd41e9d7a9f4d863e1fc7e383729976 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 29 Aug 2016 14:25:24 +0300 Subject: [PATCH 04/36] MXS-845: Ignore Maintenance state in state change logic When a server goes into maintenance, the current state is set to Maintenance and the previous state is left unmodified. The function which checks for state changes uses the current and previous values and simply compares them. Since servers in maintenance mode aren't monitored, the function always returned true when servers were in maintenance mode. When the state change to or from maintenance is ignored, the state change function works. With this fix, users can safely put servers into maintenance without having to worry about the scripts being executed. This also allows the scripts themselves to put servers into maintenance. --- server/core/monitor.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index 7f229c2bd..b74104aa3 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -823,7 +823,9 @@ mon_status_changed(MONITOR_SERVERS* mon_srv) { /* Previous status is -1 if not yet set */ return (mon_srv->mon_prev_status != -1 - && mon_srv->mon_prev_status != mon_srv->server->status); + && mon_srv->mon_prev_status != mon_srv->server->status + /** If the server is going into maintenance or coming out of it, don't trigger a state change */ + && ((mon_srv->mon_prev_status | mon_srv->server->status) & SERVER_MAINT) == 0); } /** From 71bc30be5b543282ed37b807edbaea99a2973fa4 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 30 Aug 2016 10:17:35 +0300 Subject: [PATCH 05/36] Update limitations document on wait_timeout The document now describes readwritesplit's behavior when wait_timeout is configured to a low value. --- Documentation/About/Limitations.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Documentation/About/Limitations.md b/Documentation/About/Limitations.md index 19066bb52..4b59536d2 100644 --- a/Documentation/About/Limitations.md +++ b/Documentation/About/Limitations.md @@ -26,24 +26,24 @@ Workaround: Ensure that socket paths and ports are valid. Issues [MXS-710](https://jira.mariadb.org/browse/MXS-710) and [MXS-711](https://jira.mariadb.org/browse/MXS-711) relate to this. -## Limitations with MySQL Protocol support +## Limitations with MySQL Protocol support (MySQLClient) Compression is not included in MySQL server handshake -## Limitations with Galera Cluster Monitoring +## Limitations with Galera Cluster Monitoring (galeramon) The default master selection is based only on MIN(wsrep_local_index). This can be influenced with the server priority mechanic described in the [Galera Monitor](../Monitors/Galera-Monitor.md) manual. -## Limitations in the connection router +## Limitations in the connection router (readconnroute) * If Master changes (ie. new Master promotion) during current connection the router cannot check the change. * Sending of binary data with LOAD DATA LOCAL INFILE is not supported -## Limitations in the Read/Write Splitter +## Limitations in the Read/Write Splitter (readwritesplit) Read queries are routed to the master server in the following situations: @@ -56,6 +56,20 @@ Read queries are routed to the master server in the following situations: * if there are multiple statements inside one query e.g. `INSERT INTO ... ; SELECT LAST_INSERT_ID();` +### Backend write timeout handling + +The backend connections opened by the readwritesplit will not be kept alive if +they aren't used. To keep all of the connections alive, a session command must +be periodically executed (for example `SET @a = 1;`). + +If the backend server is configured with a low +[wait_timeout](https://mariadb.com/kb/en/mariadb/server-system-variables/#wait_timeout), +it is possible that connections get closed during long sessions. It is +recommended to set the wait_timeout to a high value and let MaxScale handle the +client timeouts. This can be achieved by using the +[_connection_timeout_](../Getting-Started/Configuration-Guide.md#connection_timeout) +parameter for the service. + ### Limitations in multi-statement handling When a multi-statement query is executed through the readwritesplit @@ -177,7 +191,7 @@ possible that a slave fails to execute something because of some non-fatal, temporary failure, while the execution of the same command succeeds in other backends. -## Authentication Related Limitations +## Authentication Related Limitations (MySQLAuth) * MariaDB MaxScale can not manage authentication that uses wildcard matching in hostnames in the mysql.user table of the backend database. The only wildcards that can be @@ -191,7 +205,7 @@ succeeds in other backends. backend database. This results in failed connections and unusable usernames in MariaDB MaxScale. -## Schemarouter limitations +## Schemarouter limitations (schemarouter) The schemarouter router currently has some limitations due to the nature of the sharding implementation and the way the session variables are detected @@ -220,12 +234,12 @@ and routed. Here is a list of the current limitations. the query will be routed to the first available server. This possibly returns an error about database rights instead of a missing database. -## Dbfwfilter limitations +## Database Firewall limitations (dbfwfilter) The Database Firewall filter does not support multi-statements. Using them will result in an error being sent to the client. -## Avrorouter limitations +## Avrorouter limitations (avrorouter) The avrorouter does not support the following data types and conversions. From e54cc95a20bfdb3e9b754b30b4fc02b5db58ffca Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 30 Aug 2016 10:34:42 +0300 Subject: [PATCH 06/36] Use server weights when choosing the candidate master With this change, if two master servers both have equal depths but different weights, the one with the higher weight is used. If the depths and weights are equal, the first master listed in the configuration is used. --- Documentation/Routers/ReadConnRoute.md | 5 +++++ server/modules/routing/readconnroute.c | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Documentation/Routers/ReadConnRoute.md b/Documentation/Routers/ReadConnRoute.md index 57dd064bb..b34442526 100644 --- a/Documentation/Routers/ReadConnRoute.md +++ b/Documentation/Routers/ReadConnRoute.md @@ -30,6 +30,11 @@ running|A server that is up and running. All servers that MariaDB MaxScale can c If no `router_options` parameter is configured in the service definition, the router will use the default value of `running`. This means that it will load balance connections across all running servers defined in the `servers` parameter of the service. +When a connection is being created and the candidate server is being chosen, the +list of servers is processed in from first entry to last. This means that if two +servers with equal weight and status are found, the one that's listed first in +the _servers_ parameter for the service is chosen. + ## Limitations For a list of readconnroute limitations, please read the [Limitations](../About/Limitations.md) document. diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index a58fc7e2f..d972a3336 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -1015,12 +1015,18 @@ static BACKEND *get_root_master(BACKEND **servers) { if (servers[i] && (servers[i]->server->status & (SERVER_MASTER | SERVER_MAINT)) == SERVER_MASTER) { - if (master_host && servers[i]->server->depth < master_host->server->depth) + if (master_host == NULL) { master_host = servers[i]; } - else if (master_host == NULL) + else if (servers[i]->server->depth < master_host->server->depth || + (servers[i]->server->depth == master_host->server->depth && + servers[i]->weight > master_host->weight)) { + /** + * This master has a lower depth than the candidate master or + * the depths are equal but this master has a higher weight + */ master_host = servers[i]; } } From 94aecf4ada6c5983769d3751d96f37eea48bb5d3 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 30 Aug 2016 15:37:46 +0300 Subject: [PATCH 07/36] Prepare for local/remote admin users Local admins are the ones accessing MaxScale on the same host over a Unix domain socket, and who are strongly identified), and optional remote admins are the ones accessing MaxScale potentially over a tcp socket (potentially over the network), and who are weakly identified. These are completely separate and a different set of functions will be needed for managing them. This initial change merely renames the functions. --- server/core/adminusers.c | 41 +++++++++---------- server/core/test/testadminusers.c | 41 ++++++++++--------- server/include/adminusers.h.in | 10 ++--- server/modules/authenticator/max_admin_auth.c | 2 +- server/modules/protocol/telnetd.c | 2 +- server/modules/routing/debugcmd.c | 12 +++--- 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/server/core/adminusers.c b/server/core/adminusers.c index 0c5dd9f64..38be3f133 100644 --- a/server/core/adminusers.c +++ b/server/core/adminusers.c @@ -85,8 +85,8 @@ initialise() * @param password Password to verify * @return Non-zero if the username/password combination is valid */ -int -admin_verify(char *username, char *password) +bool +admin_remote_verify(const char *username, const char *password) { char *pw; @@ -95,23 +95,23 @@ admin_verify(char *username, char *password) { if (strcmp(username, "admin") == 0 && strcmp(password, "mariadb") == 0) { - return 1; + return true; } } else { - if ((pw = users_fetch(users, username)) == NULL) + if ((pw = users_fetch(users, (char*)username)) == NULL) // TODO: Make users const-correct. { - return 0; + return false; } struct crypt_data cdata; cdata.initialized = 0; if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0) { - return 1; + return true; } } - return 0; + return false; } @@ -184,11 +184,10 @@ loadUsers() /** * Add user * - * @param uname Name of the new user - * @return NULL on success or an error string on failure + * @param uname Name of the new user + * @return NULL on success or an error string on failure */ -char * -admin_add_user(char *uname) +const char *admin_local_add_user(const char *uname) { FILE *fp; char fname[PATH_MAX], *home; @@ -219,11 +218,11 @@ admin_add_user(char *uname) } fclose(fp); } - if (users_fetch(users, uname) != NULL) + if (users_fetch(users, (char*)uname) != NULL) // TODO: Make users const correct. { return ADMIN_ERR_DUPLICATE; } - users_add(users, uname, ""); + users_add(users, (char*)uname, ""); // TODO: Make users const correct. if ((fp = fopen(fname, "a")) == NULL) { MXS_ERROR("Unable to append to password file %s.", fname); @@ -241,8 +240,7 @@ admin_add_user(char *uname) * @param uname Name of the new user * @return NULL on success or an error string on failure */ -char* admin_remove_user( - char* uname) +const char* admin_local_remove_user(const char* uname) { FILE* fp; FILE* fp_tmp; @@ -260,14 +258,14 @@ char* admin_remove_user( return ADMIN_ERR_DELROOT; } - if (!admin_search_user(uname)) + if (!admin_local_search_user(uname)) { MXS_ERROR("Couldn't find user %s. Removing user failed.", uname); return ADMIN_ERR_USERNOTFOUND; } /** Remove user from in-memory structure */ - users_delete(users, uname); + users_delete(users, (char*)uname); // TODO: Make users const correct. /** * Open passwd file and remove user from the file. @@ -393,10 +391,9 @@ char* admin_remove_user( * Check for existance of the user * * @param user The user name to test - * @return Non-zero if the user exists + * @return True if the user exists */ -int -admin_search_user(char *user) +bool admin_local_search_user(const char *user) { initialise(); @@ -404,11 +401,11 @@ admin_search_user(char *user) if (strcmp(user, DEFAULT_ADMIN_USER) == 0) { - rv = 1; + rv = true; } else if (users) { - rv = (users_fetch(users, user) != NULL); + rv = (users_fetch(users, (char*)user) != NULL); // TODO: Make users const correct. } return rv; diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c index 2275b51ef..508c580fa 100644 --- a/server/core/test/testadminusers.c +++ b/server/core/test/testadminusers.c @@ -49,12 +49,12 @@ static int test1() { - if (admin_verify("admin", "mariadb") == 0) + if (admin_remote_verify("admin", "mariadb") == 0) { fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n"); return 1; } - if (admin_verify("bad", "user")) + if (admin_remote_verify("bad", "user")) { fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n"); return 1; @@ -73,15 +73,15 @@ test1() static int test2() { - char *err; + const char *err; - if ((err = admin_add_user("user0")) != NULL) + if ((err = admin_local_add_user("user0")) != NULL) { fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err); return 1; } - if (admin_add_user("user0") == NULL) + if (admin_local_add_user("user0") == NULL) { fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, duplicate.\n"); @@ -89,7 +89,7 @@ test2() } /* Deleting the last user is not forbidden so we expect this to succeed */ - if ((err = admin_remove_user("user0")) != NULL) + if ((err = admin_local_remove_user("user0")) != NULL) { fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err); @@ -97,7 +97,7 @@ test2() } /* Add the user back, for test5. */ - if ((err = admin_add_user("user0")) != NULL) + if ((err = admin_local_add_user("user0")) != NULL) { fprintf(stderr, "admin_add_user: test 2.4 (add user) failed, %s.\n", err); @@ -119,37 +119,37 @@ test2() static int test3() { - char *err; + const char *err; - if ((err = admin_add_user("user1")) != NULL) + if ((err = admin_local_add_user("user1")) != NULL) { fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err); return 1; } - if (admin_search_user("user1") == 0) + if (admin_local_search_user("user1") == 0) { fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n"); return 1; } - if (admin_search_user("user2") != 0) + if (admin_local_search_user("user2") != 0) { fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n"); return 1; } - if ((err = admin_remove_user("user1")) != NULL) + if ((err = admin_local_remove_user("user1")) != NULL) { fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err); return 1; } - if (admin_search_user("user1")) + if (admin_local_search_user("user1")) { fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n"); @@ -172,13 +172,14 @@ test3() static int test4() { - char *err, user[40], passwd[40]; + const char *err; + char user[40], passwd[40]; int i, n_users = 50; for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if ((err = admin_add_user(user)) != NULL) + if ((err = admin_local_add_user(user)) != NULL) { fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err); @@ -189,7 +190,7 @@ test4() for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if (admin_search_user(user) == 0) + if (admin_local_search_user(user) == 0) { fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n"); @@ -200,7 +201,7 @@ test4() for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if ((err = admin_remove_user(user)) != NULL) + if ((err = admin_local_remove_user(user)) != NULL) { fprintf(stderr, "admin_remove_user: test 4.3 (add user) failed, %s.\n", err); @@ -220,16 +221,16 @@ test4() static int test5() { - char *err; + const char *err; - if ((err = admin_add_user("user")) != NULL) + if ((err = admin_local_add_user("user")) != NULL) { fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err); return 1; } - if ((err = admin_remove_user("user0")) != NULL) + if ((err = admin_local_remove_user("user0")) != NULL) { fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err); diff --git a/server/include/adminusers.h.in b/server/include/adminusers.h.in index ea0268950..5cb692873 100644 --- a/server/include/adminusers.h.in +++ b/server/include/adminusers.h.in @@ -51,12 +51,12 @@ typedef struct admin_session #endif } ADMIN_session; -extern int admin_verify(char *, char *); -extern char *admin_add_user(char *); -extern int admin_search_user(char *); -extern void dcb_PrintAdminUsers(DCB *dcb); +extern const char *admin_local_add_user(const char *uname); +extern const char *admin_local_remove_user(const char *uname); +extern bool admin_local_search_user(const char *uname); -char* admin_remove_user(char* uname); +extern bool admin_remote_verify(const char *uname, const char *password); +extern void dcb_PrintAdminUsers(DCB *dcb); #endif diff --git a/server/modules/authenticator/max_admin_auth.c b/server/modules/authenticator/max_admin_auth.c index c90d77c65..f081e9837 100644 --- a/server/modules/authenticator/max_admin_auth.c +++ b/server/modules/authenticator/max_admin_auth.c @@ -142,7 +142,7 @@ max_admin_auth_set_protocol_data(DCB *dcb, GWBUF *buf) dcb->data = (void *)session_data; /* Check for existance of the user */ - if (admin_search_user(session_data->user)) + if (admin_local_search_user(session_data->user)) { session_data->validated = true; return 0; diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index 0dc1fbab9..38e91c330 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -202,7 +202,7 @@ static int telnetd_read_event(DCB* dcb) { *t = 0; } - if (admin_verify(telnetd->username, password)) + if (admin_remote_verify(telnetd->username, password)) { telnetd_echo(dcb, 1); telnetd->state = TELNETD_STATE_DATA; diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 035e3b837..04e8ef5a7 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -1282,15 +1282,15 @@ reload_config(DCB *dcb) static void telnetdAddUser(DCB *dcb, char *user) { - char *err; + const char *err; - if (admin_search_user(user)) + if (admin_local_search_user(user)) { dcb_printf(dcb, "User %s already exists.\n", user); return; } - if ((err = admin_add_user(user)) == NULL) + if ((err = admin_local_add_user(user)) == NULL) { dcb_printf(dcb, "User %s has been successfully added.\n", user); } @@ -1311,15 +1311,15 @@ static void telnetdRemoveUser( DCB* dcb, char* user) { - char* err; + const char* err; - if (!admin_search_user(user)) + if (!admin_local_search_user(user)) { dcb_printf(dcb, "User %s doesn't exist.\n", user); return; } - if ((err = admin_remove_user(user)) == NULL) + if ((err = admin_local_remove_user(user)) == NULL) { dcb_printf(dcb, "User %s has been successfully removed.\n", user); } From d337aa047620a398a110a95bceb842b279419afe Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 31 Aug 2016 10:44:17 +0300 Subject: [PATCH 08/36] Backport hint priority change to 2.0 The change in readwritesplit routing priorities, where hints have the highest priority, gives users more options to control how readwritesplit acts. For example, this allows read-only stored procedures to be routed to slaves by adding a hint to the query: CALL myproc(); -- maxscale route to slave The readwritesplit documentation also warns the user not to use routing hints unless they can be absolutely sure that no damage will be done. --- .../MaxScale-2.0.1-Release-Notes.md | 67 +++++++++++ Documentation/Routers/ReadWriteSplit.md | 9 +- .../routing/readwritesplit/readwritesplit.c | 110 +++++++++--------- 3 files changed, 131 insertions(+), 55 deletions(-) create mode 100644 Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md new file mode 100644 index 000000000..8cc6d28c6 --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -0,0 +1,67 @@ +# MariaDB MaxScale 2.0.1 Release Notes + +Release 2.0.1 is a GA release. + +This document describes the changes in release 2.0.1, when compared to +[release 2.0.0](MaxScale-2.0.0-Release-Notes.md). + +For any problems you encounter, please consider submitting a bug +report at [Jira](https://jira.mariadb.org). + +## License + +The license of MaxScale has been changed from GPLv2 to MariaDB BSL. + +For more information about MariaDB BSL, please refer to +[MariaDB BSL](https://www.mariadb.com/bsl). + +## Updated Features + +### Routing hint priority change + +Routing hints now have the highest priority when a routing decision is made. If +there is a conflict between the original routing decision made by the +readwritesplit and the routing hint attached to the query, the routing hint +takes higher priority. + +What this change means is that, if a query would normally be routed to the +master but the routing hint instructs the router to route it to the slave, it +would be routed to the slave. + +**WARNING**: This change can alter the way some statements are routed and could + possibly cause data loss, corruption or inconsisteny. Please consult the [Hint + Syntax](../Reference/Hint-Syntax.md) and + [ReadWriteSplit](../Routers/ReadWriteSplit.md) documentation before using + routing hints. + +## Bug fixes + +[Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20in%20(2.0.0%2C%202.0.1)%20AND%20resolved%20%3E%3D%20-21d%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC) + +* [MXS-847](https://jira.mariadb.org/browse/MXS-847): server_down event is executed 8 times due to putting sever into maintenance mode +* [MXS-845](https://jira.mariadb.org/browse/MXS-845): "Server down" event is re-triggered after maintenance mode is repeated +* [MXS-842](https://jira.mariadb.org/browse/MXS-842): Unexpected / undocumented behaviour when multiple available masters from mmmon monitor +* [MXS-846](https://jira.mariadb.org/browse/MXS-846): MMMon: Maintenance mode on slave logs error message every second + + +## Known Issues and Limitations + +There are some limitations and known issues within this version of MaxScale. +For more information, please refer to the [Limitations](../About/Limitations.md) +document. + +## Packaging + +RPM and Debian packages are provided for the Linux distributions supported +by MariaDB Enterprise. + +Packages can be downloaded [here](https://mariadb.com/resources/downloads). + +## Source Code + +The source code of MaxScale is tagged at GitHub with a tag, which is identical +with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale +is maxscale-X.Y.Z. Further, *master* always refers to the latest released +non-beta version. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). diff --git a/Documentation/Routers/ReadWriteSplit.md b/Documentation/Routers/ReadWriteSplit.md index d59a7c49f..b45f0b4b0 100644 --- a/Documentation/Routers/ReadWriteSplit.md +++ b/Documentation/Routers/ReadWriteSplit.md @@ -150,7 +150,14 @@ reconnecting to MariaDB MaxScale once a new master is available. ## Routing hints -The readwritesplit router supports routing hints. For a detailed guide on hint syntax and functionality, please read [this](../Reference/Hint-Syntax.md) document. +The readwritesplit router supports routing hints. For a detailed guide on hint +syntax and functionality, please read [this](../Reference/Hint-Syntax.md) document. + +**Note**: Routing hints will always have the highest priority when a routing +decision is made. This means that it is possible to cause inconsistencies in +the session state and the actual data in the database by adding routing hints +to DDL/DML statements which are then directed to slave servers. Only use routing +hints when you are sure that they can cause no harm. ## Limitations diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 7620c939b..0c980a631 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -1375,59 +1375,7 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, { target = TARGET_MASTER; } - /** process routing hints */ - while (hint != NULL) - { - if (hint->type == HINT_ROUTE_TO_MASTER) - { - target = TARGET_MASTER; /*< override */ - MXS_DEBUG("%lu [get_route_target] Hint: route to master.", - pthread_self()); - break; - } - else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) - { - /** - * Searching for a named server. If it can't be - * found, the oroginal target is chosen. - */ - target |= TARGET_NAMED_SERVER; - MXS_DEBUG("%lu [get_route_target] Hint: route to " - "named server : ", - pthread_self()); - } - else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER) - { - /** not implemented */ - } - else if (hint->type == HINT_ROUTE_TO_ALL) - { - /** not implemented */ - } - else if (hint->type == HINT_PARAMETER) - { - if (strncasecmp((char *)hint->data, "max_slave_replication_lag", - strlen("max_slave_replication_lag")) == 0) - { - target |= TARGET_RLAG_MAX; - } - else - { - MXS_ERROR("Unknown hint parameter " - "'%s' when 'max_slave_replication_lag' " - "was expected.", - (char *)hint->data); - } - } - else if (hint->type == HINT_ROUTE_TO_SLAVE) - { - target = TARGET_SLAVE; - MXS_DEBUG("%lu [get_route_target] Hint: route to " - "slave.", - pthread_self()); - } - hint = hint->next; - } /*< while (hint != NULL) */ + /** If nothing matches then choose the master */ if ((target & (TARGET_ALL | TARGET_SLAVE | TARGET_MASTER)) == 0) { @@ -1436,7 +1384,6 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, } else { - /** hints don't affect on routing */ ss_dassert(trx_active || (QUERY_IS_TYPE(qtype, QUERY_TYPE_WRITE) || QUERY_IS_TYPE(qtype, QUERY_TYPE_MASTER_READ) || @@ -1460,6 +1407,61 @@ static route_target_t get_route_target(ROUTER_CLIENT_SES *rses, QUERY_IS_TYPE(qtype, QUERY_TYPE_UNKNOWN))); target = TARGET_MASTER; } + + /** process routing hints */ + while (hint != NULL) + { + if (hint->type == HINT_ROUTE_TO_MASTER) + { + target = TARGET_MASTER; /*< override */ + MXS_DEBUG("%lu [get_route_target] Hint: route to master.", + pthread_self()); + break; + } + else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) + { + /** + * Searching for a named server. If it can't be + * found, the oroginal target is chosen. + */ + target |= TARGET_NAMED_SERVER; + MXS_DEBUG("%lu [get_route_target] Hint: route to " + "named server : ", + pthread_self()); + } + else if (hint->type == HINT_ROUTE_TO_UPTODATE_SERVER) + { + /** not implemented */ + } + else if (hint->type == HINT_ROUTE_TO_ALL) + { + /** not implemented */ + } + else if (hint->type == HINT_PARAMETER) + { + if (strncasecmp((char *)hint->data, "max_slave_replication_lag", + strlen("max_slave_replication_lag")) == 0) + { + target |= TARGET_RLAG_MAX; + } + else + { + MXS_ERROR("Unknown hint parameter " + "'%s' when 'max_slave_replication_lag' " + "was expected.", + (char *)hint->data); + } + } + else if (hint->type == HINT_ROUTE_TO_SLAVE) + { + target = TARGET_SLAVE; + MXS_DEBUG("%lu [get_route_target] Hint: route to " + "slave.", + pthread_self()); + } + hint = hint->next; + } /*< while (hint != NULL) */ + #if defined(SS_EXTRA_DEBUG) MXS_INFO("Selected target \"%s\"", STRTARGET(target)); #endif From a9b0a5550c98e745c56fe2e801c7ef4accc9a354 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 30 Aug 2016 14:33:00 +0300 Subject: [PATCH 09/36] Allow socket and address/port to be used with maxadmin It's now possible to use both a Unix domain socket and host/port when connecting with MaxAdmin to MaxScale. By default MaxAdmin will attempt to use the default Unix domain socket, but if host and/or port has been specified, then an inet socket will be used. maxscaled will authenticate the connection attempt differently depending on whether a Unix domain socket is used or not. If a Unix domain socket is used, then the Linux user id will be used for the authorization, otherwise the 1.4.3 username/password handshake will be performed. adminusers has now been extended so that there is one set of functions for local users (connecting locally over a Unix socket) and one set of functions for remote users (connecting locally or remotely over an Inet socket). The local users are stored in the new .../maxscale-users and the remote users in .../passwd. That is, the old users of a 1.4 installation will work as such in 2.0. One difference is that there will be *no* default remote user. That is, remote users will always have to be added manually using a local user. The implementation is shared; the local and remote alternatives use common functions to which the hashtable and filename to be used are forwarded. The commands "[add|remove] user" behave now exactly like they did in 1.4.3, and also all existing users work out of the box. In addition there is now the commands "[enable|disable] account" using which Linux accounts can be enabled for MaxAdmin usage. --- Documentation/Reference/MaxAdmin.md | 176 +++++-- .../MaxScale-2.0.1-Release-Notes.md | 50 +- Documentation/Routers/CLI.md | 34 +- ...era-Cluster-Connection-Routing-Tutorial.md | 3 - ...a-Cluster-Read-Write-Splitting-Tutorial.md | 3 - ...Replication-Connection-Routing-Tutorial.md | 2 - ...plication-Read-Write-Splitting-Tutorial.md | 2 - Documentation/Tutorials/Nagios-Plugins.md | 3 +- .../Tutorials/RabbitMQ-And-Tee-Archiving.md | 2 +- .../Upgrading/Upgrading-To-MaxScale-2.0.md | 37 +- client/maxadmin.c | 390 ++++++++++++-- server/core/adminusers.c | 492 ++++++++++++------ server/core/test/testadminusers.c | 32 +- server/include/adminusers.h.in | 12 +- server/modules/authenticator/max_admin_auth.c | 2 +- server/modules/include/maxadmin.h | 17 +- server/modules/include/maxscaled.h | 15 +- server/modules/protocol/maxscaled.c | 203 +++++--- server/modules/protocol/telnetd.c | 2 +- server/modules/routing/debugcmd.c | 149 ++++-- 20 files changed, 1175 insertions(+), 451 deletions(-) diff --git a/Documentation/Reference/MaxAdmin.md b/Documentation/Reference/MaxAdmin.md index 55ab1da2b..fefb53dbf 100644 --- a/Documentation/Reference/MaxAdmin.md +++ b/Documentation/Reference/MaxAdmin.md @@ -3,6 +3,7 @@ # The Maxscale Administrative & Monitoring Client Application - [Overview](#overview) + - [Configuring MariaDB MaxScale for MaxAdmin](#configuring) - [Running MaxAdmin](#running) - [Working With Administration Interface Users](#interface) - [Getting Help](#help) @@ -14,7 +15,6 @@ - [Working with Monitors](#monitors) - [MariaDB MaxScale Status Commands](#statuscommands) - [Administration Commands](#admincommands) - - [Configuring MariaDB MaxScale to Accept MaxAdmin Connections](#connections) - [Tuning MariaDB MaxScale](#tuning) @@ -30,53 +30,145 @@ MaxAdmin supports * Execution of command scripts + +# Configuring MariaDB MaxScale for MaxAdmin + +In order to be able to use MaxAdmin, MariaDB MaxScale must be configured for it. + +There are two ways MaxAdmin can connect to to MaxScale. + +* Using a Unix domain socket. +* Using a hostname and port. + +The first alternative is introduced in MaxScale 2.0 and is the secure and +recommended way. The second alternative is available for backward compatibility, +but is _insecure_ and **deprecated** and _will be removed in a future version of +MaxScale_. + +An example configuration looks as follows: + +``` +[MaxAdmin] +type=service +router=cli + +[MaxAdmin Unix Listener] +type=listener +service=MaxAdmin +protocol=maxscaled +socket=default + +[MaxAdmin Inet Listener] +type=listener +service=MaxAdmin +protocol=maxscaled +address=localhost +port=6603 +``` + +In the configuration above, two listeners are created; one listening on the default +Unix domain socket and one listening on the default port. + +Which approach is used has other implications than just how the communication between +MaxAdmin and MariaDB MaxScale is handled. In the former case, the authorization is +based upon the Linux identity and in the latter case on explicitly created user +accounts that have **no** relationship to the Linux accounts. + +Note that if the socket path or port are changed, then MaxAdmin has to be invoked +with `-S` or `-P` respectively. + # Running MaxAdmin +Depending on whether MariaDB MaxScale has been configured to use Unix domain sockets +or internet sockets, MaxAdmin needs to be invoked slightly differently. + +If Unix domain sockets are used, then MaxAdmin needs no additional arguments: + + alice@host$ maxadmin + MaxAdmin> + +The above implies that the Linux user _alice_ has been enabled to use MaxAdmin. + +If internet sockets are used, then either the host, port, user or password has +to be specified explicitly: + + alice@host$ maxadmin -u maxscale-admin + Password: + MaxScale> + +If Unix domain sockets are used, then initially only `root` has access. MaxAdmin +usage can subsequently be enabled for other Linux users. + The MaxAdmin client application may be run in two different modes, either as an interactive command shell for executing commands against MariaDB MaxScale or by passing commands on the MaxAdmin command line itself. # Working With Administration Interface Users -MaxScale communicates with MariaDB MaxScale using UNIX domain sockets, which means that it can only be used on the very host where MariaDB MaxScale is running. Initially MaxAdmin can connect only when run as `root`. Other Linux users can subsequently be allowed access. - -**NOTE**: Remote access with -h and -P options is no longer supported. - ## What Users Have Been Defined? -In order to see the current users (UNIX users) that have been defined for the administration interface use the command _show users_. +In order to see the Linux users for whom MaxAdmin usage has been enabled and +any explicitly created accounts, use the command _show users_. MaxScale> show users - Administration interface users: - User names: vilho, dba, massi, mark + Enabled Linux accounts (secure) : alice, bob, cecil + Created network accounts (insecure): maxscale-admin MaxScale> -Please note `root` will not be shown. +Please note that `root` will not be shown. - MaxScale> show users - Administration interface users: - No administration users have been defined. - MaxScale> +## Enabling a Linux account + +To enable MaxAdmin usage for a particular Linux account, use the command _enable account_. +This command is passed a user name, which should be the same as that of an existing Linux user. + + MaxScale> enable account bob + +Note that it is not checked that the provided name indeed corresponds to an existing +Linux account, so it is possible to enable an account that does not exist yet. + +Note also that it is possible to enable a Linux account irrespective of how MaxAdmin +has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin +users connecting over a Unix domain socket. + +## Disabling a Linux account + +To disable MaxAdmin usage for a particular Linux account, use the command _disable account_. +This command is passed a user name, which should be a Linux user for whom MaxAdmin usage +earlier has been enabled. + + MaxScale> disable account bob + +Note also that it is possible to disable a Linux account irrespective of how MaxAdmin +has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin +users connecting over a Unix domain socket. + +Note that it is possible to disable the current user, but that will only affect the +next attempt to use MaxAdmin. `root` cannot be removed. ## Add A New User -To add a new administrative user to the MariaDB MaxScale server use the command _add user_. This command is passed a user name, which should be the same as that of an existing Linux user. +To add a new MaxAdmin user to be used when MaxAdmin connects over an internet socket, +use the command _add user_. This command is passed a user name and a password. - MaxScale> add user maria - User maria has been successfully added. + MaxScale> add user maxscale-admin secretpwd + User maxscale-admin has been successfully added. MaxScale> -Note that a user that is given the rights to use MaxAdmin, has the very same rights `root` has, including adding and removing users. +Note that there is no difference in rights between an enabled Linux account and an +explicitly created user. ## Delete A User -To remove a user the command _remove user_ is used and it is simply invoked with the user to be removed. +To remove a user the command _remove user_ is used and it is invoked with the +username and password. - MaxScale> remove user maria - User maria has been successfully removed. + MaxScale> remove user maxscale-admin secretpwd + User maxscale-admin has been successfully removed. MaxScale> -Note that it is possible to remove the current user, but that will only affect the next attempt to use MaxAdmin. `root` cannot be removed. +Note that it is possible to remove the current user, but that will only affect the +next attempt to use MaxAdmin. # Command Line Switches @@ -93,6 +185,26 @@ The MaxAdmin command accepts a number of switches --socket=... The UNIX domain socket path that MaxAdmin will use to connect to the MariaDB MaxScale server. If no -S option is given then the default socket path /tmp/maxadmin.sock will be used. + + -u user + --user=... + Sets the username that will be used for the MaxScale connection. If no -u option is passed on the MaxAdmin command line then the default username of ‘admin’ will be used. + + + -p password + --password=... + Sets the user password that will be used. If no -p option is passed on the command line then MaxAdmin will prompt for interactive entry of the password. + + + -h hostname + --hostname=... + The hostname of the MaxScale server to connect to. If no -h option is passed on the command line then MaxAdmin will attempt to connect to the host ‘localhost’. + + + -P port + --port=... + The port that MaxAdmin will use to connect to the MaxScale server. if no -P option is given then the default port of 6603 will be used. + -? --help @@ -835,28 +947,6 @@ A command, _reload config_, is available that will cause MariaDB MaxScale to rel The MariaDB MaxScale server may be shutdown using the _shutdown maxscale_ command. - -# Configuring MariaDB MaxScale to Accept MaxAdmin Connections - -In order to enable the use of MaxAdmin, the service CLI with an accompanying listener must be added to the MariaDB MaxScale configuration file. - -The default entries required are shown below. - - [CLI] - type=service - router=cli - - [CLI Listener] - type=listener - service=CLI - protocol=maxscaled - socket=default - #socket=/somepath/maxadmin.socket - -**NOTE**: As the protocol maxscaled only supports UNIX domain sockets it is thus not possible to connect remotely to MariaDB MaxScale. - -In the example, the default socket path (/tmp/maxadmin.sock) is used. It can be changed to an specific path and in that case MaxAdmin must be invoked with the -S option. - # Tuning MariaDB MaxScale diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md index 8cc6d28c6..9f7b538bd 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -5,16 +5,12 @@ Release 2.0.1 is a GA release. This document describes the changes in release 2.0.1, when compared to [release 2.0.0](MaxScale-2.0.0-Release-Notes.md). +If you are upgrading from 1.4.3, please also read the release notes +of [2.0.0](./MaxScale-2.0.0-Release-Notes.md). + For any problems you encounter, please consider submitting a bug report at [Jira](https://jira.mariadb.org). -## License - -The license of MaxScale has been changed from GPLv2 to MariaDB BSL. - -For more information about MariaDB BSL, please refer to -[MariaDB BSL](https://www.mariadb.com/bsl). - ## Updated Features ### Routing hint priority change @@ -34,6 +30,42 @@ would be routed to the slave. [ReadWriteSplit](../Routers/ReadWriteSplit.md) documentation before using routing hints. +### MaxAdmin Usage + +In 2.0.0 (Beta), the authentication mechanism of MaxAdmin was completely +changed, so that MaxAdmin could only connect to MaxScale using a Unix domain +socket, thus _only when run on the same host_, and authorization was based +on the Unix identity. Remote access was no longer supported. + +To the user this was visible so that while you in 1.4.3 had to provide +a password when starting _maxadmin_ and when adding a user +``` +user@host $ maxadmin -p password +MaxAdmin> add user john johns-password +``` +in 2.0.0 (Beta), where only Unix domain sockets could be used, you did not +have to provide a password neither when starting _maxadmin_, nor when adding +users +``` +user@host $ maxadmin +MaxAdmin> add user john +``` +as the MaxScale user corresponded to a Unix user, provided the Linux user +had been added as a user of MaxScale. + +In 2.0.1 (GA) this has been changed so that the 1.4.3 behaviour is intact +but _deprecated_, and the 2.0.0 (Beta) behaviour is exposed using a new set +of commands: +``` +MaxAdmin> enable account alice +MaxAdmin> disable account alice +``` +Note that the way you need to invoke _maxadmin_ depends upon how MariaDB +MaxScale has been configued. + +Please consult +[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details. + ## Bug fixes [Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20in%20(2.0.0%2C%202.0.1)%20AND%20resolved%20%3E%3D%20-21d%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC) @@ -43,12 +75,10 @@ would be routed to the slave. * [MXS-842](https://jira.mariadb.org/browse/MXS-842): Unexpected / undocumented behaviour when multiple available masters from mmmon monitor * [MXS-846](https://jira.mariadb.org/browse/MXS-846): MMMon: Maintenance mode on slave logs error message every second - ## Known Issues and Limitations There are some limitations and known issues within this version of MaxScale. -For more information, please refer to the [Limitations](../About/Limitations.md) -document. +For more information, please refer to the [Limitations](../About/Limitations.md) document. ## Packaging diff --git a/Documentation/Routers/CLI.md b/Documentation/Routers/CLI.md index dd35785f2..5d088e1fd 100644 --- a/Documentation/Routers/CLI.md +++ b/Documentation/Routers/CLI.md @@ -1,10 +1,13 @@ # CLI -The command line interface as used by `maxadmin`. This is a variant of the debugcli that is built slightly differently so that it may be accessed by the client application `maxadmin`. The CLI requires the use of the `maxscaled` protocol. +The command line interface as used by `maxadmin`. The _CLI_ router requires the use +of the `maxscaled` protocol. ## Configuration -There are two components to the definition required in order to run the command line interface to use with MaxAdmin; a service and a listener. +Two components are required in order to run the command line interface for use with +_maxadmin_; a service and a listener. The listener may either use a Unix domain socket +or an internet socket. The default entries required are shown below. @@ -13,9 +16,34 @@ The default entries required are shown below. type=service router=cli -[CLI Listener] +# Unix Domain Socket +[CLI Unix Listener] type=listener service=CLI protocol=maxscaled socket=default + +# Internet Socket +[CLI Inet Listener] +type=listener +service=CLI +protocol=maxscaled +address=localhost +port=6603 ``` +In the example above, two listeners have been specified; one that listens on the +default Unix domain socket and one that listens on the default port. In the latter +case, if the `address=` entry is removed, connections are allowed from any machine +on your network. + +In the former case, if the value of `socket` is changed and in the latter case, +if the value of `port` is changed, _maxadmin_ must be invoked with the `-S` and +`-P` options respectively. + +Note that if Unix domain sockets are used, the connection is secure, but _maxadmin_ +can only be used on the same host where MariaDB MaxScale runs. If internet sockets +are used, the connection is _inherently insecure_ but _maxadmin_ can be used from +another host than the one where MariaDB MaxScale runs. + +Note that the latter approach is **deprecated** and will be removed in a future +version of MariaDB MaxScale. diff --git a/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md b/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md index a5d006aed..b37e61008 100644 --- a/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md +++ b/Documentation/Tutorials/Galera-Cluster-Connection-Routing-Tutorial.md @@ -117,9 +117,6 @@ The final stage in the configuration is to add the option service which is used protocol=maxscaled socket=default -**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set. -Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled. - ## Starting MariaDB MaxScale Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface. diff --git a/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md b/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md index 8204a72aa..383496b80 100644 --- a/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md +++ b/Documentation/Tutorials/Galera-Cluster-Read-Write-Splitting-Tutorial.md @@ -147,9 +147,6 @@ protocol=maxscaled socket=default ``` -**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set. -Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled. - ## Starting MariaDB MaxScale Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface. diff --git a/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md b/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md index aa25f5f34..597fdf717 100644 --- a/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md +++ b/Documentation/Tutorials/MySQL-Replication-Connection-Routing-Tutorial.md @@ -174,8 +174,6 @@ service=CLI protocol=maxscaled socket=default ``` -**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set. -Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled. # Starting MariaDB MaxScale diff --git a/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md b/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md index f5d17acc5..ae52c0c8c 100644 --- a/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md +++ b/Documentation/Tutorials/MySQL-Replication-Read-Write-Splitting-Tutorial.md @@ -133,8 +133,6 @@ service=CLI protocol=maxscaled socket=default ``` -**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set. -Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled. # Starting MariaDB MaxScale diff --git a/Documentation/Tutorials/Nagios-Plugins.md b/Documentation/Tutorials/Nagios-Plugins.md index 18010b8b9..ebf896af3 100644 --- a/Documentation/Tutorials/Nagios-Plugins.md +++ b/Documentation/Tutorials/Nagios-Plugins.md @@ -62,8 +62,7 @@ service=AdminInterface protocol=maxscaled socket=default ``` -**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set. -Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled. + ## Prepare Nagios configuration files. Assuming Nagios installed on a separated server and the plugins are in /usr/lib64/nagios/plugins and configuration files are in /etc/nagios: diff --git a/Documentation/Tutorials/RabbitMQ-And-Tee-Archiving.md b/Documentation/Tutorials/RabbitMQ-And-Tee-Archiving.md index 2688e4abf..996c35e0d 100644 --- a/Documentation/Tutorials/RabbitMQ-And-Tee-Archiving.md +++ b/Documentation/Tutorials/RabbitMQ-And-Tee-Archiving.md @@ -208,7 +208,7 @@ router=cli type=listener service=MaxAdmin Service protocol=maxscaled -port=6603 +socket=default ``` Now we have created the MariaDB MaxScale configuration file and all we need to do is to save diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md index a1ca08064..8da56d2ec 100644 --- a/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.0.md @@ -12,37 +12,18 @@ configuration file. ## MaxAdmin -The way a user of MaxAdmin is authenticated has been completely changed. -In 2.0, MaxAdmin can only connect to MariaDB MaxScale using a domain socket, thus -_only when run on the same host_, and authorization is based upon the UNIX -identity. Remote access is no longer supported. +The default way the communication between MaxAdmin and MariaDB MaxScale is +handled has been changed from an internet socket to a Unix domain socket. +The former alternative is still available but has been _deprecated_. -When 2.0 has been installed, MaxAdmin can only be used by `root` and -other users must be added anew. Please consult -[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details. +If no arguments are given to MaxAdmin, it will attempt to connect to +MariaDB MaxScale using a Unix domain socket. After the upgrade you will +need to provide at least one internet socket related flag - `-h`, `-P`, +`-u` or `-p` - to force MaxAdmin to use the internet socket approach. -This change requires the _maxscaled_ protocol listener entry in the -MaxScale configuration file to be updated; address and port information -must be replaced with socket information. For instance, an entry like -``` -[MaxAdmin Listener] -type=listener -protocol=maxscaled -address=localhost -port=6603 -``` -should be updated to -``` -[MaxAdmin Listener] -type=listener -protocol=maxscaled -socket=default -``` -where `default` corresponds to `/tmp/maxadmin.sock`. +E.g. -Note that if this update is *not* made, maxscaled will log a warning -and use the default socket path. This behaviour may change in later -releases of MaxScale. + user@host $ maxadmin -u admin ## MySQL Monitor diff --git a/client/maxadmin.c b/client/maxadmin.c index cb4375b6c..1c9cc8d93 100644 --- a/client/maxadmin.c +++ b/client/maxadmin.c @@ -27,6 +27,7 @@ * @endverbatim */ +#include #include #include #include @@ -45,6 +46,7 @@ #include #include #include +#include #include @@ -60,15 +62,21 @@ #define STRERROR_BUFLEN 512 #endif -static int connectMaxScale(char *socket); -static int setipaddress(struct in_addr *a, char *p); -static int authMaxScale(int so); +#define MAX_PASSWORD_LEN 80 + +static int connectUsingUnixSocket(const char *socket); +static int connectUsingInetSocket(const char *hostname, const char *port, + const char *user, const char* password); +static int setipaddress(struct in_addr *a, const char *p); +static bool authUnixSocket(int so); +static bool authInetSocket(int so, const char *user, const char *password); static int sendCommand(int so, char *cmd); static void DoSource(int so, char *cmd); static void DoUsage(const char*); static int isquit(char *buf); static void PrintVersion(const char *progname); static void read_inifile(char **, int*); +static bool getPassword(char *password, size_t length); #ifdef HISTORY @@ -79,10 +87,15 @@ prompt(EditLine *el __attribute__((__unused__))) return prompt; } + #endif static struct option long_options[] = { + {"host", required_argument, 0, 'h'}, + {"user", required_argument, 0, 'u'}, + {"password", required_argument, 0, 'p'}, + {"port", required_argument, 0, 'P'}, {"socket", required_argument, 0, 'S'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, '?'}, @@ -90,6 +103,10 @@ static struct option long_options[] = {0, 0, 0, 0} }; +#define MAXADMIN_DEFAULT_HOST "localhost" +#define MAXADMIN_DEFAULT_PORT "6603" +#define MAXADMIN_DEFAULT_USER "admin" + /** * The main for the maxadmin client * @@ -112,7 +129,12 @@ main(int argc, char **argv) #else char buf[1024]; #endif - char *conn_socket = MAXADMIN_DEFAULT_SOCKET; + char *hostname = NULL; + char *port = NULL; + char *user = NULL; + char *passwd = NULL; + char *conn_socket = NULL; + char *default_socket = MAXADMIN_DEFAULT_SOCKET; int use_emacs = 0; int so; int option_index = 0; @@ -120,11 +142,28 @@ main(int argc, char **argv) read_inifile(&conn_socket, &use_emacs); - while ((c = getopt_long(argc, argv, "S:v?e", + while ((c = getopt_long(argc, argv, "h:p:P:u:S:v?e", long_options, &option_index)) >= 0) { switch (c) { + case 'h': + hostname = strdup(optarg); + break; + + case 'p': + passwd = strdup(optarg); + memset(optarg, '\0', strlen(optarg)); + break; + + case 'P': + port = strdup(optarg); + break; + + case 'u': + user = strdup(optarg); + break; + case 'S': conn_socket = strdup(optarg); break; @@ -138,22 +177,72 @@ main(int argc, char **argv) break; case '?': - DoUsage(*argv); + DoUsage(argv[0]); exit(optopt ? EXIT_FAILURE : EXIT_SUCCESS); } } - /* Connet to MaxScale using UNIX domain socket */ - if ((so = connectMaxScale(conn_socket)) == -1) + if ((hostname || port || user || passwd) && (conn_socket)) { - exit(1); + // Either socket or any parameters related to hostname/port. + DoUsage(argv[0]); + exit(EXIT_FAILURE); } - /* Check for successful authentication */ - if (!authMaxScale(so)) + if (hostname || port || user || passwd) { - fprintf(stderr, "Failed to connect to MaxScale, incorrect username.\n"); - exit(1); + assert(!conn_socket); + + if (!hostname) + { + hostname = MAXADMIN_DEFAULT_HOST; + } + + if (!port) + { + port = MAXADMIN_DEFAULT_PORT; + } + + if (!user) + { + user = MAXADMIN_DEFAULT_USER; + } + } + else + { + if (!conn_socket) + { + conn_socket = MAXADMIN_DEFAULT_SOCKET; + } + } + + assert(!((hostname || port) && conn_socket)); + + if (conn_socket) + { + if ((so = connectUsingUnixSocket(conn_socket)) == -1) + { + exit(EXIT_FAILURE); + } + } + else + { + char password[MAX_PASSWORD_LEN]; + + if (passwd == NULL) + { + if (!getPassword(password, MAX_PASSWORD_LEN)) + { + exit(EXIT_FAILURE); + } + + passwd = password; + } + + if ((so = connectUsingInetSocket(hostname, port, user, passwd)) == -1) + { + exit(EXIT_FAILURE); + } } if (optind < argc) @@ -309,44 +398,114 @@ main(int argc, char **argv) * @return The connected socket or -1 on error */ static int -connectMaxScale(char *conn_socket) +connectUsingUnixSocket(const char *conn_socket) { - struct sockaddr_un local_addr; - int so; - int keepalive = 1; - int optval = 1; + int so = -1; - if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) + { + struct sockaddr_un local_addr; + + memset(&local_addr, 0, sizeof local_addr); + local_addr.sun_family = AF_UNIX; + strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path) - 1); + + if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) == 0) + { + int keepalive = 1; + if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) + { + fprintf(stderr, "Warning: Could not set keepalive.\n"); + } + + /* Client is sending connection credentials (Pid, User, Group) */ + int optval = 1; + if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == 0) + { + if (!authUnixSocket(so)) + { + close(so); + so = -1; + } + } + else + { + char errbuf[STRERROR_BUFLEN]; + fprintf(stderr, "Could not set SO_PASSCRED: %s\n", + strerror_r(errno, errbuf, sizeof(errbuf))); + close(so); + so = -1; + } + } + else + { + char errbuf[STRERROR_BUFLEN]; + fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n", + conn_socket, strerror_r(errno, errbuf, sizeof(errbuf))); + close(so); + so = -1; + } + } + else { char errbuf[STRERROR_BUFLEN]; fprintf(stderr, "Unable to create socket: %s\n", strerror_r(errno, errbuf, sizeof(errbuf))); - return -1; } - memset(&local_addr, 0, sizeof local_addr); - local_addr.sun_family = AF_UNIX; - strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path)-1); + return so; +} - if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) +/** + * Connect to the MaxScale server + * + * @param hostname The hostname to connect to + * @param port The port to use for the connection + * @return The connected socket or -1 on error + */ +static int +connectUsingInetSocket(const char *hostname, const char *port, + const char *user, const char *passwd) +{ + int so; + + if ((so = socket(AF_INET, SOCK_STREAM, 0)) != -1) + { + struct sockaddr_in addr; + + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + setipaddress(&addr.sin_addr, hostname); + addr.sin_port = htons(atoi(port)); + + if (connect(so, (struct sockaddr *) &addr, sizeof(addr)) == 0) + { + int keepalive = 1; + if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) + { + fprintf(stderr, "Warning: Could not set keepalive.\n"); + } + + if (!authInetSocket(so, user, passwd)) + { + close(so); + so = -1; + } + } + else + { + char errbuf[STRERROR_BUFLEN]; + fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n", + hostname, port, strerror_r(errno, errbuf, sizeof(errbuf))); + close(so); + so = -1; + } + } + else { char errbuf[STRERROR_BUFLEN]; - fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n", - conn_socket, strerror_r(errno, errbuf, sizeof(errbuf))); - close(so); - return -1; - } - - if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive))) - { - perror("setsockopt"); - } - - /* Client is sending connection credentials (Pid, User, Group) */ - if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) != 0) - { - fprintf(stderr, "SO_PASSCRED failed\n"); - return -1; + fprintf(stderr, "Unable to create socket: %s\n", + strerror_r(errno, errbuf, sizeof(errbuf))); } return so; @@ -360,7 +519,7 @@ connectMaxScale(char *conn_socket) * @return 1 on success, 0 on failure */ static int -setipaddress(struct in_addr *a, char *p) +setipaddress(struct in_addr *a, const char *p) { #ifdef __USE_POSIX struct addrinfo *ai = NULL, hint; @@ -418,17 +577,84 @@ setipaddress(struct in_addr *a, char *p) * @param so The socket connected to MaxScale * @return Non-zero of succesful authentication */ -static int -authMaxScale(int so) +static bool +authUnixSocket(int so) { char buf[MAXADMIN_AUTH_REPLY_LEN]; if (read(so, buf, MAXADMIN_AUTH_REPLY_LEN) != MAXADMIN_AUTH_REPLY_LEN) { + fprintf(stderr, "Could not read authentication response from MaxScale.\n"); return 0; } - return strncmp(buf, MAXADMIN_FAILED_AUTH_MESSAGE, MAXADMIN_AUTH_REPLY_LEN); + bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0); + + if (!authenticated) + { + fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n"); + } + + return authenticated; +} + +/** + * Perform authentication using the maxscaled protocol conventions + * + * @param so The socket connected to MaxScale + * @param user The username to authenticate + * @param password The password to authenticate with + * @return Non-zero of succesful authentication + */ +static bool +authInetSocket(int so, const char *user, const char *password) +{ + char buf[20]; + size_t len; + + len = MAXADMIN_AUTH_USER_PROMPT_LEN; + if (read(so, buf, len) != len) + { + fprintf(stderr, "Could not read user prompt from MaxScale.\n"); + return false; + } + + len = strlen(user); + if (write(so, user, len) != len) + { + fprintf(stderr, "Could not write user to MaxScale.\n"); + return false; + } + + len = MAXADMIN_AUTH_PASSWORD_PROMPT_LEN; + if (read(so, buf, len) != len) + { + fprintf(stderr, "Could not read password prompt from MaxScale.\n"); + return false; + } + + len = strlen(password); + if (write(so, password, len) != len) + { + fprintf(stderr, "Could not write password to MaxScale.\n"); + return false; + } + + len = MAXADMIN_AUTH_REPLY_LEN; + if (read(so, buf, len) != len) + { + fprintf(stderr, "Could not read authentication response from MaxScale.\n"); + return false; + } + + bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0); + + if (!authenticated) + { + fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n"); + } + + return authenticated; } /** @@ -550,13 +776,27 @@ DoUsage(const char *progname) { PrintVersion(progname); printf("The MaxScale administrative and monitor client.\n\n"); - printf("Usage: %s [-S socket] [ | ]\n\n", progname); - printf(" -S|--socket=... The UNIX socket to connect to, The default is\n"); - printf(" /tmp/maxadmin.sock\n"); - printf(" -v|--version print version information and exit\n"); - printf(" -?|--help Print this help text.\n"); + printf("Usage: %s [(-S socket)|([-u user] [-p password] [-h hostname] [-P port])]" + "[ | ]\n\n", progname); + printf(" -S|--socket=... The UNIX domain socket to connect to, The default is\n"); + printf(" %s\n", MAXADMIN_DEFAULT_SOCKET); + printf(" -u|--user=... The user name to use for the connection, default\n"); + printf(" is %s.\n", MAXADMIN_DEFAULT_USER); + printf(" -p|--password=... The user password, if not given the password will\n"); + printf(" be prompted for interactively\n"); + printf(" -h|--host=... The maxscale host to connecto to. The default is\n"); + printf(" %s\n", MAXADMIN_DEFAULT_HOST); + printf(" -P|--port=... The port to use for the connection, the default\n"); + printf(" port is %s.\n", MAXADMIN_DEFAULT_PORT); + printf(" -v|--version Print version information and exit\n"); + printf(" -?|--help Print this help text.\n"); + printf("\n"); printf("Any remaining arguments are treated as MaxScale commands or a file\n"); printf("containing commands to execute.\n"); + printf("\n"); + printf("Either a socket or a hostname/port combination should be provided.\n"); + printf("If a port or hostname is provided, but not the other, then the default\n" + "value is used.\n"); } /** @@ -687,3 +927,55 @@ read_inifile(char **conn_socket, int* editor) } fclose(fp); } + +/** + * Get password + * + * @param password Buffer for password. + * @param len The size of the buffer. + * + * @return Whether the password was obtained. + */ +bool getPassword(char *passwd, size_t len) +{ + bool gotten = false; + + struct termios tty_attr; + tcflag_t c_lflag; + + if (tcgetattr(STDIN_FILENO, &tty_attr) == 0) + { + c_lflag = tty_attr.c_lflag; + tty_attr.c_lflag &= ~ICANON; + tty_attr.c_lflag &= ~ECHO; + + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0) + { + printf("Password: "); + fgets(passwd, len, stdin); + + tty_attr.c_lflag = c_lflag; + + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0) + { + int i = strlen(passwd); + + if (i > 1) + { + passwd[i - 1] = '\0'; + } + + printf("\n"); + + gotten = true; + } + } + } + + if (!gotten) + { + fprintf(stderr, "Could not configure terminal.\n"); + } + + return gotten; +} diff --git a/server/core/adminusers.c b/server/core/adminusers.c index 38be3f133..cb748c417 100644 --- a/server/core/adminusers.c +++ b/server/core/adminusers.c @@ -37,13 +37,25 @@ * 23/07/13 Mark Riddoch Addition of error mechanism to add user * 23/05/16 Massimiliano Pinto admin_add_user and admin_remove_user * no longer accept password parameter + * 02/09/16 Johan Wikman Enabled Linux accounts and MaxScale users * * @endverbatim */ -static USERS *loadUsers(); static void initialise(); -static USERS *users = NULL; +static USERS *loadLinuxUsers(); +static USERS *loadInetUsers(); + +static const char *admin_add_user(USERS** pusers, const char* fname, + const char* uname, const char* password); +static const char* admin_remove_user(USERS *users, const char* fname, + const char *uname, const char *passwd); +static bool admin_search_user(USERS *users, const char *uname); + + + +static USERS *linux_users = NULL; +static USERS *inet_users = NULL; static int admin_init = 0; static char *ADMIN_ERR_NOMEM = "Out of memory"; @@ -61,7 +73,11 @@ static char *ADMIN_SUCCESS = NULL; static const int LINELEN = 80; -static const char USERS_FILE_NAME[] = "maxadmin-users"; +static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users"; +static const char INET_USERS_FILE_NAME[] = "passwd"; + +static const char INET_DEFAULT_USERNAME[] = "admin"; +static const char INET_DEFAULT_PASSWORD[] = "mariadb"; /** * Admin Users initialisation @@ -75,124 +91,15 @@ initialise() } admin_init = 1; - users = loadUsers(); + linux_users = loadLinuxUsers(); + inet_users = loadInetUsers(); } -/** - * Verify a username and password - * - * @param username Username to verify - * @param password Password to verify - * @return Non-zero if the username/password combination is valid - */ -bool -admin_remote_verify(const char *username, const char *password) -{ - char *pw; - - initialise(); - if (users == NULL) - { - if (strcmp(username, "admin") == 0 && strcmp(password, "mariadb") == 0) - { - return true; - } - } - else - { - if ((pw = users_fetch(users, (char*)username)) == NULL) // TODO: Make users const-correct. - { - return false; - } - struct crypt_data cdata; - cdata.initialized = 0; - if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0) - { - return true; - } - } - return false; -} - - -/** - * Load the admin users - * - * @return Table of users - */ -static USERS * -loadUsers() -{ - USERS *rval; - FILE *fp; - char fname[PATH_MAX], *home; - char uname[80]; - int added_users = 0; - - initialise(); - snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME); - if ((fp = fopen(fname, "r")) == NULL) - { - return NULL; - } - if ((rval = users_alloc()) == NULL) - { - fclose(fp); - return NULL; - } - while (fgets(uname, sizeof(uname), fp)) - { - char *nl = strchr(uname, '\n'); - - if (nl) - { - *nl = '\0'; - } - else if (!feof(fp)) - { - MXS_ERROR("Line length exceeds %d characters, possible corrupted " - "'passwd' file in: %s", LINELEN, fname); - users_free(rval); - rval = NULL; - break; - } - - char *tmp_ptr = strchr(uname, ':'); - if (tmp_ptr) - { - *tmp_ptr = '\0'; - MXS_WARNING("Found user '%s' with password. " - "This user might not be compatible with new maxadmin in MaxScale 2.0. " - "Remove it with \"remove user %s\" through MaxAdmin", uname, uname); - } - if (users_add(rval, uname, "")) - { - added_users++; - } - } - fclose(fp); - - if (!added_users) - { - users_free(rval); - rval = NULL; - } - - return rval; -} - -/** - * Add user - * - * @param uname Name of the new user - * @return NULL on success or an error string on failure - */ -const char *admin_local_add_user(const char *uname) +static const char *admin_add_user(USERS** pusers, const char* fname, + const char* uname, const char* password) { FILE *fp; - char fname[PATH_MAX], *home; - - initialise(); + char path[PATH_MAX], *home; if (access(get_datadir(), F_OK) != 0) { @@ -202,50 +109,51 @@ const char *admin_local_add_user(const char *uname) } } - snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME); - if (users == NULL) + snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname); + if (*pusers == NULL) { MXS_NOTICE("Create initial password file."); - if ((users = users_alloc()) == NULL) + if ((*pusers = users_alloc()) == NULL) { return ADMIN_ERR_NOMEM; } - if ((fp = fopen(fname, "w")) == NULL) + if ((fp = fopen(path, "w")) == NULL) { - MXS_ERROR("Unable to create password file %s.", fname); + MXS_ERROR("Unable to create password file %s.", path); return ADMIN_ERR_PWDFILEOPEN; } fclose(fp); } - if (users_fetch(users, (char*)uname) != NULL) // TODO: Make users const correct. + if (users_fetch(*pusers, (char*)uname) != NULL) // TODO: Make users const correct. { return ADMIN_ERR_DUPLICATE; } - users_add(users, (char*)uname, ""); // TODO: Make users const correct. - if ((fp = fopen(fname, "a")) == NULL) + users_add(*pusers, (char*)uname, password ? (char*)password : ""); // TODO: Make users const correct. + if ((fp = fopen(path, "a")) == NULL) { - MXS_ERROR("Unable to append to password file %s.", fname); + MXS_ERROR("Unable to append to password file %s.", path); return ADMIN_ERR_FILEAPPEND; } - fprintf(fp, "%s\n", uname); + if (password) + { + fprintf(fp, "%s:%s\n", uname, password); + } + else + { + fprintf(fp, "%s\n", uname); + } fclose(fp); return ADMIN_SUCCESS; } - -/** - * Remove maxscale user from in-memory structure and from password file - * - * @param uname Name of the new user - * @return NULL on success or an error string on failure - */ -const char* admin_local_remove_user(const char* uname) +static const char* admin_remove_user(USERS *users, const char* fname, + const char *uname, const char *passwd) { FILE* fp; FILE* fp_tmp; - char fname[PATH_MAX]; - char fname_tmp[PATH_MAX]; + char path[PATH_MAX]; + char path_tmp[PATH_MAX]; char* home; char fusr[LINELEN]; char fpwd[LINELEN]; @@ -258,43 +166,53 @@ const char* admin_local_remove_user(const char* uname) return ADMIN_ERR_DELROOT; } - if (!admin_local_search_user(uname)) + if (!admin_search_user(users, uname)) { MXS_ERROR("Couldn't find user %s. Removing user failed.", uname); return ADMIN_ERR_USERNOTFOUND; } + if (passwd) + { + if (admin_verify_inet_user(uname, passwd) == 0) + { + MXS_ERROR("Authentication failed, wrong user/password " + "combination. Removing user failed."); + return ADMIN_ERR_AUTHENTICATION; + } + } + /** Remove user from in-memory structure */ users_delete(users, (char*)uname); // TODO: Make users const correct. /** * Open passwd file and remove user from the file. */ - snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME); - snprintf(fname_tmp, sizeof(fname_tmp), "%s/%s_tmp", get_datadir(), USERS_FILE_NAME); + snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname); + snprintf(path_tmp, sizeof(path_tmp), "%s/%s_tmp", get_datadir(), fname); /** * Rewrite passwd file from memory. */ - if ((fp = fopen(fname, "r")) == NULL) + if ((fp = fopen(path, "r")) == NULL) { int err = errno; MXS_ERROR("Unable to open password file %s : errno %d.\n" "Removing user from file failed; it must be done " "manually.", - fname, + path, err); return ADMIN_ERR_PWDFILEOPEN; } /** * Open temporary passwd file. */ - if ((fp_tmp = fopen(fname_tmp, "w")) == NULL) + if ((fp_tmp = fopen(path_tmp, "w")) == NULL) { int err = errno; MXS_ERROR("Unable to open tmp file %s : errno %d.\n" "Removing user from passwd file failed; it must be done " "manually.", - fname_tmp, + path_tmp, err); fclose(fp); return ADMIN_ERR_TMPFILEOPEN; @@ -309,11 +227,11 @@ const char* admin_local_remove_user(const char* uname) MXS_ERROR("Unable to process passwd file %s : errno %d.\n" "Removing user from file failed, and must be done " "manually.", - fname, + path, err); fclose(fp); fclose(fp_tmp); - unlink(fname_tmp); + unlink(path_tmp); return ADMIN_ERR_PWDFILEACCESS; } @@ -328,7 +246,7 @@ const char* admin_local_remove_user(const char* uname) else if (!feof(fp)) { MXS_ERROR("Line length exceeds %d characters, possible corrupted " - "'passwd' file in: %s", LINELEN, fname); + "'passwd' file in: %s", LINELEN, path); fclose(fp); fclose(fp_tmp); return ADMIN_ERR_PWDFILEACCESS; @@ -356,11 +274,11 @@ const char* admin_local_remove_user(const char* uname) "errno %d.\n" "Removing user from file failed, and must be " "done manually.", - fname, + path, err); fclose(fp); fclose(fp_tmp); - unlink(fname_tmp); + unlink(path_tmp); return ADMIN_ERR_PWDFILEACCESS; } } @@ -368,16 +286,16 @@ const char* admin_local_remove_user(const char* uname) /** * Replace original passwd file with new. */ - if (rename(fname_tmp, fname)) + if (rename(path_tmp, path)) { int err = errno; MXS_ERROR("Unable to rename new passwd file %s : errno " "%d.\n" "Rename it to %s manually.", - fname_tmp, + path_tmp, err, - fname); - unlink(fname_tmp); + path); + unlink(path_tmp); fclose(fp_tmp); return ADMIN_ERR_PWDFILEACCESS; } @@ -385,46 +303,284 @@ const char* admin_local_remove_user(const char* uname) return ADMIN_SUCCESS; } - - /** * Check for existance of the user * - * @param user The user name to test + * @param uname The user name to test * @return True if the user exists */ -bool admin_local_search_user(const char *user) +static bool admin_search_user(USERS *users, const char *uname) +{ + return (users_fetch(users, (char*)uname) != NULL); // TODO: Make users const correct. +} + +/** + */ +void dcb_print_users(DCB *dcb, const char* heading, USERS *users) +{ + dcb_printf(dcb, "%s", heading); + + if (users) + { + HASHITERATOR *iter = hashtable_iterator(users->data); + + if (iter) + { + char *sep = ""; + const char *user; + + while ((user = hashtable_next(iter)) != NULL) + { + dcb_printf(dcb, "%s%s", sep, user); + sep = ", "; + } + + hashtable_iterator_free(iter); + } + } + + dcb_printf(dcb, "%s", "\n"); +} + +/** + * Load the admin users + * + * @return Table of users + */ +static USERS * +loadUsers(const char *fname) +{ + USERS *rval; + FILE *fp; + char path[PATH_MAX], *home; + char uname[80]; + int added_users = 0; + + initialise(); + snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname); + if ((fp = fopen(path, "r")) == NULL) + { + return NULL; + } + if ((rval = users_alloc()) == NULL) + { + fclose(fp); + return NULL; + } + while (fgets(uname, sizeof(uname), fp)) + { + char *nl = strchr(uname, '\n'); + + if (nl) + { + *nl = '\0'; + } + else if (!feof(fp)) + { + MXS_ERROR("Line length exceeds %d characters, possibly corrupted " + "'passwd' file in: %s", LINELEN, path); + users_free(rval); + rval = NULL; + break; + } + + char *password; + char *colon = strchr(uname, ':'); + if (colon) + { + // Inet case + *colon = 0; + password = colon + 1; + } + else + { + // Linux case. + password = ""; + } + + if (users_add(rval, uname, password)) + { + added_users++; + } + } + fclose(fp); + + if (!added_users) + { + users_free(rval); + rval = NULL; + } + + return rval; +} + + +static USERS *loadLinuxUsers() +{ + return loadUsers(LINUX_USERS_FILE_NAME); +} + +static USERS *loadInetUsers() +{ + return loadUsers(INET_USERS_FILE_NAME); +} + +/** + * Enable Linux account + * + * @param uname Name of Linux user + * + * @return NULL on success or an error string on failure. + */ +const char *admin_enable_linux_account(const char *uname) { initialise(); - int rv = 0; + return admin_add_user(&linux_users, LINUX_USERS_FILE_NAME, uname, NULL); +} - if (strcmp(user, DEFAULT_ADMIN_USER) == 0) +/** + * Disable Linux account + * + * @param uname Name of Linux user + * + * @return NULL on success or an error string on failure. + */ +const char* admin_disable_linux_account(const char* uname) +{ + initialise(); + + return admin_remove_user(linux_users, LINUX_USERS_FILE_NAME, uname, NULL); +} + +/** + * Check whether Linux account is enabled + * + * @param uname The user name + * + * @return True if the account is enabled, false otherwise. + */ +bool admin_linux_account_enabled(const char *uname) +{ + initialise(); + + bool rv = false; + + if (strcmp(uname, DEFAULT_ADMIN_USER) == 0) { rv = true; } - else if (users) + else if (linux_users) { - rv = (users_fetch(users, (char*)user) != NULL); // TODO: Make users const correct. + rv = admin_search_user(linux_users, uname); } return rv; } /** - * Print the statistics and user names of the administration users + * Add insecure remote (network) user. * - * @param dcb A DCB to send the output to + * @param uname Name of the new user. + * @param password Password of the new user. + * + * @return NULL on success or an error string on failure. */ -void -dcb_PrintAdminUsers(DCB *dcb) +const char *admin_add_inet_user(const char *uname, const char* password) { - if (users) + initialise(); + + struct crypt_data cdata; + cdata.initialized = 0; + char *cpassword = crypt_r(password, ADMIN_SALT, &cdata); + + return admin_add_user(&inet_users, INET_USERS_FILE_NAME, uname, cpassword); +} + +/** + * Remove insecure remote (network) user + * + * @param uname Name of user to be removed. + * @param password Password of user to be removed. + * + * @return NULL on success or an error string on failure. + */ +const char* admin_remove_inet_user(const char* uname, const char *password) +{ + initialise(); + + return admin_remove_user(inet_users, INET_USERS_FILE_NAME, uname, password); +} + +/** + * Check for existance of remote user. + * + * @param user The user name to test. + * + * @return True if the user exists, false otherwise. + */ +bool admin_inet_user_exists(const char *uname) +{ + initialise(); + + bool rv = false; + + if (inet_users) { - dcb_usersPrint(dcb, users); + rv = admin_search_user(inet_users, uname); + } + + return rv; +} + +/** + * Verify a remote user name and password + * + * @param username Username to verify + * @param password Password to verify + * + * @return True if the username/password combination is valid + */ +bool +admin_verify_inet_user(const char *username, const char *password) +{ + bool rv = false; + + initialise(); + + if (inet_users) + { + const char* pw = users_fetch(inet_users, (char*)username); // TODO: Make users const-correct. + + if (pw) + { + struct crypt_data cdata; + cdata.initialized = 0; + if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0) + { + rv = true; + } + } } else { - dcb_printf(dcb, "No administration users have been defined.\n"); + if (strcmp(username, INET_DEFAULT_USERNAME) == 0 + && strcmp(password, INET_DEFAULT_PASSWORD) == 0) + { + rv = true; + } } + + return rv; +} + +/** + * Print Linux and and inet users + * + * @param dcb A DCB to send the output to. + */ +void dcb_PrintAdminUsers(DCB *dcb) +{ + dcb_print_users(dcb, "Enabled Linux accounts (secure) : ", linux_users); + dcb_print_users(dcb, "Created network accounts (insecure): ", inet_users); } diff --git a/server/core/test/testadminusers.c b/server/core/test/testadminusers.c index 508c580fa..5c7dcbf2e 100644 --- a/server/core/test/testadminusers.c +++ b/server/core/test/testadminusers.c @@ -49,12 +49,12 @@ static int test1() { - if (admin_remote_verify("admin", "mariadb") == 0) + if (admin_verify_inet_user("admin", "mariadb") == 0) { fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n"); return 1; } - if (admin_remote_verify("bad", "user")) + if (admin_verify_inet_user("bad", "user")) { fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n"); return 1; @@ -75,13 +75,13 @@ test2() { const char *err; - if ((err = admin_local_add_user("user0")) != NULL) + if ((err = admin_enable_linux_account("user0")) != NULL) { fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err); return 1; } - if (admin_local_add_user("user0") == NULL) + if (admin_enable_linux_account("user0") == NULL) { fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, duplicate.\n"); @@ -89,7 +89,7 @@ test2() } /* Deleting the last user is not forbidden so we expect this to succeed */ - if ((err = admin_local_remove_user("user0")) != NULL) + if ((err = admin_disable_linux_account("user0")) != NULL) { fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err); @@ -97,7 +97,7 @@ test2() } /* Add the user back, for test5. */ - if ((err = admin_local_add_user("user0")) != NULL) + if ((err = admin_enable_linux_account("user0")) != NULL) { fprintf(stderr, "admin_add_user: test 2.4 (add user) failed, %s.\n", err); @@ -121,35 +121,35 @@ test3() { const char *err; - if ((err = admin_local_add_user("user1")) != NULL) + if ((err = admin_enable_linux_account("user1")) != NULL) { fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err); return 1; } - if (admin_local_search_user("user1") == 0) + if (admin_linux_account_enabled("user1") == 0) { fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n"); return 1; } - if (admin_local_search_user("user2") != 0) + if (admin_linux_account_enabled("user2") != 0) { fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n"); return 1; } - if ((err = admin_local_remove_user("user1")) != NULL) + if ((err = admin_disable_linux_account("user1")) != NULL) { fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err); return 1; } - if (admin_local_search_user("user1")) + if (admin_linux_account_enabled("user1")) { fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n"); @@ -179,7 +179,7 @@ test4() for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if ((err = admin_local_add_user(user)) != NULL) + if ((err = admin_enable_linux_account(user)) != NULL) { fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err); @@ -190,7 +190,7 @@ test4() for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if (admin_local_search_user(user) == 0) + if (admin_linux_account_enabled(user) == 0) { fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n"); @@ -201,7 +201,7 @@ test4() for (i = 1; i < n_users; i++) { sprintf(user, "user%d", i); - if ((err = admin_local_remove_user(user)) != NULL) + if ((err = admin_disable_linux_account(user)) != NULL) { fprintf(stderr, "admin_remove_user: test 4.3 (add user) failed, %s.\n", err); @@ -223,14 +223,14 @@ test5() { const char *err; - if ((err = admin_local_add_user("user")) != NULL) + if ((err = admin_enable_linux_account("user")) != NULL) { fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err); return 1; } - if ((err = admin_local_remove_user("user0")) != NULL) + if ((err = admin_disable_linux_account("user0")) != NULL) { fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err); diff --git a/server/include/adminusers.h.in b/server/include/adminusers.h.in index 5cb692873..bee9d9a7b 100644 --- a/server/include/adminusers.h.in +++ b/server/include/adminusers.h.in @@ -51,11 +51,15 @@ typedef struct admin_session #endif } ADMIN_session; -extern const char *admin_local_add_user(const char *uname); -extern const char *admin_local_remove_user(const char *uname); -extern bool admin_local_search_user(const char *uname); +extern const char *admin_enable_linux_account(const char *uname); +extern const char *admin_disable_linux_account(const char *uname); +extern bool admin_linux_account_enabled(const char *uname); -extern bool admin_remote_verify(const char *uname, const char *password); +extern const char *admin_add_inet_user(const char *uname, const char *password); +extern const char *admin_remove_inet_user(const char *uname, const char *password); +extern bool admin_inet_user_exists(const char *uname); + +extern bool admin_verify_inet_user(const char *uname, const char *password); extern void dcb_PrintAdminUsers(DCB *dcb); diff --git a/server/modules/authenticator/max_admin_auth.c b/server/modules/authenticator/max_admin_auth.c index f081e9837..19b59f33d 100644 --- a/server/modules/authenticator/max_admin_auth.c +++ b/server/modules/authenticator/max_admin_auth.c @@ -142,7 +142,7 @@ max_admin_auth_set_protocol_data(DCB *dcb, GWBUF *buf) dcb->data = (void *)session_data; /* Check for existance of the user */ - if (admin_local_search_user(session_data->user)) + if (admin_linux_account_enabled(session_data->user)) { session_data->validated = true; return 0; diff --git a/server/modules/include/maxadmin.h b/server/modules/include/maxadmin.h index aba632259..0022f1706 100644 --- a/server/modules/include/maxadmin.h +++ b/server/modules/include/maxadmin.h @@ -13,12 +13,19 @@ * Public License. */ -#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock" +#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock" #define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG_LEN 7 -#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default" -#define MAXADMIN_GETPWUID_BUF_LEN 255 -#define MAXADMIN_AUTH_REPLY_LEN 6 -#define MAXADMIN_FAILED_AUTH_MESSAGE "FAILED" +#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default" + +#define MAXADMIN_AUTH_REPLY_LEN 6 +#define MAXADMIN_AUTH_FAILED_REPLY "FAILED" +#define MAXADMIN_AUTH_SUCCESS_REPLY "OK----" + +#define MAXADMIN_AUTH_USER_PROMPT "USER" +#define MAXADMIN_AUTH_USER_PROMPT_LEN 4 + +#define MAXADMIN_AUTH_PASSWORD_PROMPT "PASSWORD" +#define MAXADMIN_AUTH_PASSWORD_PROMPT_LEN 8 #endif diff --git a/server/modules/include/maxscaled.h b/server/modules/include/maxscaled.h index cba26f7f8..1267ea50e 100644 --- a/server/modules/include/maxscaled.h +++ b/server/modules/include/maxscaled.h @@ -28,16 +28,17 @@ #include #include /** - * The telnetd specific protocol structure to put in the DCB. + * The maxscaled specific protocol structure to put in the DCB. */ -typedef struct maxscaled +typedef struct maxscaled { - SPINLOCK lock; /**< Protocol structure lock */ - int state; /**< The connection state */ - char *username; /**< The login name of the user */ + SPINLOCK lock; /**< Protocol structure lock */ + int state; /**< The connection state */ + char *username; /**< The login name of the user */ } MAXSCALED; -#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for credentials */ -#define MAXSCALED_STATE_DATA 2 /**< User logged in */ +#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for user */ +#define MAXSCALED_STATE_PASSWD 2 /**< Waiting for password */ +#define MAXSCALED_STATE_DATA 3 /**< User logged in */ #endif diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index 41ae11377..1fc583599 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -65,6 +65,8 @@ MODULE_INFO info = static char *version_str = "V2.0.0"; +#define GETPWUID_BUF_LEN 255 + static int maxscaled_read_event(DCB* dcb); static int maxscaled_write_event(DCB *dcb); static int maxscaled_write(DCB *dcb, GWBUF *queue); @@ -75,6 +77,98 @@ static int maxscaled_close(DCB *dcb); static int maxscaled_listen(DCB *dcb, char *config); static char *mxsd_default_auth(); +static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb) +{ + bool authenticated = false; + + struct ucred ucred; + socklen_t len = sizeof(struct ucred); + + /* Get UNIX client credentials from socket*/ + if (getsockopt(dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) + { + struct passwd pw_entry; + struct passwd *pw_tmp; + char buf[GETPWUID_BUF_LEN]; + + /* Fetch username from UID */ + if (getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp) == 0) + { + GWBUF *username; + + /* Set user in protocol */ + protocol->username = strdup(pw_entry.pw_name); + + username = gwbuf_alloc(strlen(protocol->username) + 1); + + strcpy(GWBUF_DATA(username), protocol->username); + + /* Authenticate the user */ + if (dcb->authfunc.extract(dcb, username) == 0 && + dcb->authfunc.authenticate(dcb) == 0) + { + dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY); + protocol->state = MAXSCALED_STATE_DATA; + dcb->user = strdup(protocol->username); + } + else + { + dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY); + } + + authenticated = true; + } + else + { + MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'", + (unsigned long)ucred.uid); + } + } + else + { + MXS_ERROR("Failed to get UNIX domain socket credentials for 'MaxScale Admin'."); + } + + return authenticated; +} + + +static bool authenticate_inet_socket(MAXSCALED *protocol, DCB *dcb) +{ + dcb_printf(dcb, MAXADMIN_AUTH_USER_PROMPT); + return true; +} + +static bool authenticate_socket(MAXSCALED *protocol, DCB *dcb) +{ + bool authenticated = false; + + struct sockaddr address; + socklen_t address_len = sizeof(address); + + if (getsockname(dcb->fd, &address, &address_len) == 0) + { + if (address.sa_family == AF_UNIX) + { + authenticated = authenticate_unix_socket(protocol, dcb); + } + else + { + authenticated = authenticate_inet_socket(protocol, dcb); + } + } + else + { + char errbuf[STRERROR_BUFLEN]; + + MXS_ERROR("Could not get socket family of client connection: %s", + strerror_r(errno, errbuf, sizeof(errbuf))); + } + + return authenticated; +} + + /** * The "module object" for the maxscaled protocol module. */ @@ -160,10 +254,41 @@ static int maxscaled_read_event(DCB* dcb) { if (GWBUF_LENGTH(head)) { - if (maxscaled->state == MAXSCALED_STATE_DATA) + switch (maxscaled->state) { - SESSION_ROUTE_QUERY(dcb->session, head); - dcb_printf(dcb, "OK"); + case MAXSCALED_STATE_LOGIN: + { + maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + maxscaled->state = MAXSCALED_STATE_PASSWD; + dcb_printf(dcb, MAXADMIN_AUTH_PASSWORD_PROMPT); + gwbuf_free(head); + } + break; + + case MAXSCALED_STATE_PASSWD: + { + char *password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + if (admin_verify_inet_user(maxscaled->username, password)) + { + dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY); + maxscaled->state = MAXSCALED_STATE_DATA; + } + else + { + dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY); + maxscaled->state = MAXSCALED_STATE_LOGIN; + } + gwbuf_free(head); + free(password); + } + break; + + case MAXSCALED_STATE_DATA: + { + SESSION_ROUTE_QUERY(dcb->session, head); + dcb_printf(dcb, "OK"); + } + break; } } else @@ -240,9 +365,9 @@ static int maxscaled_accept(DCB *listener) while ((client_dcb = dcb_accept(listener, &MyObject)) != NULL) { - MAXSCALED *maxscaled_protocol = NULL; + MAXSCALED *maxscaled_protocol = (MAXSCALED *)calloc(1, sizeof(MAXSCALED)); - if ((maxscaled_protocol = (MAXSCALED *)calloc(1, sizeof(MAXSCALED))) == NULL) + if (!maxscaled_protocol) { dcb_close(client_dcb); continue; @@ -251,52 +376,14 @@ static int maxscaled_accept(DCB *listener) maxscaled_protocol->username = NULL; maxscaled_protocol->state = MAXSCALED_STATE_LOGIN; - /* Get UNIX client credentials from socket*/ - if (getsockopt(client_dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) + bool authenticated = false; + + if (!authenticate_socket(maxscaled_protocol, client_dcb)) { - MXS_ERROR("Failed to get UNIX socket credentials for 'MaxScale Admin'"); dcb_close(client_dcb); + free(maxscaled_protocol); continue; } - else - { - struct passwd pw_entry; - struct passwd *pw_tmp; - char buf[MAXADMIN_GETPWUID_BUF_LEN]; - - /* Fetch username from UID */ - if (!getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp)) - { - GWBUF *username; - - /* Set user in protocol */ - maxscaled_protocol->username = strdup(pw_entry.pw_name); - - username = gwbuf_alloc(strlen(maxscaled_protocol->username) + 1); - - strcpy(GWBUF_DATA(username), maxscaled_protocol->username); - - /* Authenticate the user */ - if (client_dcb->authfunc.extract(client_dcb, username) == 0 && - client_dcb->authfunc.authenticate(client_dcb) == 0) - { - dcb_printf(client_dcb, "OK----"); - maxscaled_protocol->state = MAXSCALED_STATE_DATA; - client_dcb->user = strdup(maxscaled_protocol->username); - } - else - { - dcb_printf(client_dcb, "FAILED"); - } - } - else - { - MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'", - (unsigned long)ucred.uid); - dcb_close(client_dcb); - continue; - } - } spinlock_init(&maxscaled_protocol->lock); client_dcb->protocol = (void *)maxscaled_protocol; @@ -358,27 +445,7 @@ static int maxscaled_listen(DCB *listener, char *config) } else { - const char* colon = strchr(config, ':'); - ss_dassert(colon); - const char* port = colon + 1; - - if (*port == '0') - { - // It seems "socket=socket-path" has been specified. - - // The colon etc. will be stripped away in dcb_listen_create_socket_unix. - socket_path = config; - } - else - { - MXS_WARNING("The 'maxscaled' protocol can only be used with a Unix domain socket, but " - "it seems to have been configured with an address and port: %s. " - "Using the default socket path instead: %s. " - "Remove all 'address' and 'port' entries from maxscaled protocol " - "listeners and replace with 'socket=default' or 'socket=path-to-socket'.", - config, MAXADMIN_DEFAULT_SOCKET); - socket_path = MAXADMIN_DEFAULT_SOCKET; - } + socket_path = config; } return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1; diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index 38e91c330..ce0fc4e6d 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -202,7 +202,7 @@ static int telnetd_read_event(DCB* dcb) { *t = 0; } - if (admin_remote_verify(telnetd->username, password)) + if (admin_verify_inet_user(telnetd->username, password)) { telnetd_echo(dcb, 1); telnetd->state = TELNETD_STATE_DATA; diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 04e8ef5a7..8e0be70f1 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -205,8 +205,8 @@ struct subcommand showoptions[] = { "Show the status of the polling threads in MaxScale", {0, 0, 0} }, { "users", 0, telnetdShowUsers, - "Show statistics and user names for the debug interface", - "Show statistics and user names for the debug interface", + "Show all maxadmin enabled Linux accounts and created maxadmin users", + "Show all maxadmin enabled Linux accounts and created maxadmin users", {0, 0, 0} }, { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } @@ -427,6 +427,8 @@ static void enable_syslog(); static void disable_syslog(); static void enable_maxlog(); static void disable_maxlog(); +static void enable_account(DCB *, char *user); +static void disable_account(DCB *, char *user); /** * * The subcommands of the enable command @@ -514,6 +516,16 @@ struct subcommand enableoptions[] = { "Enable maxlog logging", {0, 0, 0} }, + { + "account", + 1, + enable_account, + "Enable maxadmin usage for Linux user. E.g.:\n" + " MaxScale> enable account alice", + "Enable maxadmin usage for Linux user. E.g.:\n" + " MaxScale> enable account alice", + {ARG_TYPE_STRING, 0, 0} + }, { NULL, 0, @@ -612,6 +624,16 @@ struct subcommand disableoptions[] = { "Disable maxlog logging", {0, 0, 0} }, + { + "account", + 1, + disable_account, + "Disable maxadmin usage for Linux user. E.g.:\n" + " MaxScale> disable account alice", + "Disable maxadmin usage for Linux user. E.g.:\n" + " MaxScale> disable account alice", + {ARG_TYPE_STRING, 0, 0} + }, { NULL, 0, @@ -666,32 +688,38 @@ struct subcommand failoptions[] = { }; #endif /* FAKE_CODE */ -static void telnetdAddUser(DCB *, char *); +static void telnetdAddUser(DCB *, char *user, char *password); + /** * The subcommands of the add command */ struct subcommand addoptions[] = { - { "user", 1, telnetdAddUser, - "Add a new user for the debug interface. E.g. add user john", - "Add a new user for the debug interface. E.g. add user john", - {ARG_TYPE_STRING, 0, 0} }, + { "user", 2, telnetdAddUser, + "Add insecure account for using maxadmin over the network. E.g.:\n" + " MaxScale> add user bob somepass", + "Add insecure account for using maxadmin over the network. E.g.:\n" + " MaxScale> add user bob somepass", + {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; -static void telnetdRemoveUser(DCB *, char *); +static void telnetdRemoveUser(DCB *, char *user, char *password); + /** * The subcommands of the remove command */ struct subcommand removeoptions[] = { { "user", - 1, + 2, telnetdRemoveUser, - "Remove existing maxscale user. Example : remove user john", - "Remove existing maxscale user. Example : remove user john", - {ARG_TYPE_STRING, 0, 0} + "Remove account for using maxadmin over the network. E.g.:\n" + " MaxAdmin> remove user bob somepass", + "Remove account for using maxadmin over the network. E.g.:\n" + " MaxAdmin> remove user bob somepass", + {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { NULL, 0, NULL, NULL, NULL, {0, 0, 0} @@ -1274,63 +1302,61 @@ reload_config(DCB *dcb) } /** - * Add a new maxscale admin user + * Add a new remote (insecure, over the network) maxscale admin user * - * @param dcb The DCB for messages - * @param user The user name + * @param dcb The DCB for messages + * @param user The user name + * @param user The user password */ static void -telnetdAddUser(DCB *dcb, char *user) +telnetdAddUser(DCB *dcb, char *user, char *password) { const char *err; - if (admin_local_search_user(user)) + if (admin_inet_user_exists(user)) { - dcb_printf(dcb, "User %s already exists.\n", user); + dcb_printf(dcb, "Account %s for remote (network) usage already exists.\n", user); return; } - if ((err = admin_local_add_user(user)) == NULL) + if ((err = admin_add_inet_user(user, password)) == NULL) { - dcb_printf(dcb, "User %s has been successfully added.\n", user); + dcb_printf(dcb, "Account %s for remote (network) usage has been successfully added.\n", user); } else { - dcb_printf(dcb, "Failed to add new user. %s\n", err); + dcb_printf(dcb, "Failed to add new remote account %s: %s.\n", user, err); } } - /** - * Remove a maxscale admin user + * Remove a remote (insecure, over the network) maxscale admin user * - * @param dcb The DCB for messages - * @param user The user name + * @param dcb The DCB for messages + * @param user The user name + * @param user The user password */ -static void telnetdRemoveUser( - DCB* dcb, - char* user) +static void telnetdRemoveUser(DCB *dcb, char *user, char *password) { const char* err; - if (!admin_local_search_user(user)) + if (!admin_inet_user_exists(user)) { - dcb_printf(dcb, "User %s doesn't exist.\n", user); + dcb_printf(dcb, "Account %s for remote (network) usage does not exist.\n", user); return; } - if ((err = admin_local_remove_user(user)) == NULL) + if ((err = admin_remove_inet_user(user, password)) == NULL) { - dcb_printf(dcb, "User %s has been successfully removed.\n", user); + dcb_printf(dcb, "Account %s for remote (network) usage has been successfully removed.\n", user); } else { - dcb_printf(dcb, "Failed to remove user %s. %s\n", user, err); + dcb_printf(dcb, "Failed to remove remote account %s: %s\n", user, err); } } - /** * Print the adminsitration users * @@ -1339,7 +1365,6 @@ static void telnetdRemoveUser( static void telnetdShowUsers(DCB *dcb) { - dcb_printf(dcb, "Administration interface users:\n"); dcb_PrintAdminUsers(dcb); } @@ -1829,6 +1854,60 @@ disable_maxlog() mxs_log_set_maxlog_enabled(false); } +/** + * Enable a Linux account + * + * @param dcb The DCB for messages + * @param user The Linux user name + */ +static void +enable_account(DCB *dcb, char *user) +{ + const char *err; + + if (admin_linux_account_enabled(user)) + { + dcb_printf(dcb, "The Linux user %s has already been enabled.\n", user); + return; + } + + if ((err = admin_enable_linux_account(user)) == NULL) + { + dcb_printf(dcb, "The Linux user %s has successfully been enabled.\n", user); + } + else + { + dcb_printf(dcb, "Failed to enable the Linux user %s: %s\n", user, err); + } +} + +/** + * Disable a Linux account + * + * @param dcb The DCB for messages + * @param user The Linux user name + */ +static void +disable_account(DCB *dcb, char *user) +{ + const char* err; + + if (!admin_linux_account_enabled(user)) + { + dcb_printf(dcb, "The Linux user %s has not been enabled.\n", user); + return; + } + + if ((err = admin_disable_linux_account(user)) == NULL) + { + dcb_printf(dcb, "The Linux user %s has successfully been disabled.\n", user); + } + else + { + dcb_printf(dcb, "Failed to disable the Linux user %s: %s\n", user, err); + } +} + #if defined(FAKE_CODE) static void fail_backendfd(void) { From c7907ed72896315a4ebe5a4126476d10a93f0ef2 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 2 Sep 2016 13:58:34 +0300 Subject: [PATCH 10/36] Fix minor leak in maxscaled.c --- server/modules/protocol/maxscaled.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index 1fc583599..281eff65e 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -116,6 +116,8 @@ static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb) dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY); } + gwbuf_free(username); + authenticated = true; } else From 741ba5e4442b23799cc8ab9e9ea74e16076aad4c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 5 Sep 2016 10:17:37 +0300 Subject: [PATCH 11/36] Fix links to MaxScale repo The links now point to the correct repo. --- Documentation/Release-Notes/MaxScale-2.0.0-Release-Notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Release-Notes/MaxScale-2.0.0-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.0-Release-Notes.md index d0857be98..b55281936 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.0-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.0-Release-Notes.md @@ -126,4 +126,4 @@ The source code of MaxScale is tagged at GitHub with a tag, which is identical with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale is X.Y.Z. Further, *master* always refers to the latest released non-beta version. -The source code is available [here](https://github.com/mariadb-corporation/maxscale-bsl). +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). From 4e3de4c56d17fd56333c9c032a1d94f94c771a30 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 5 Sep 2016 07:06:51 +0300 Subject: [PATCH 12/36] Rename and relocate CDC Python examples Moved the CDC example scripts into the protocol directory and added the .py suffix. Fixed all references to these scripts. --- Documentation/Protocols/CDC.md | 5 +++++ Documentation/Protocols/CDC_users.md | 5 ++--- Documentation/Routers/Avrorouter.md | 22 +++++++++---------- server/modules/protocol/CMakeLists.txt | 1 + .../modules/protocol/examples/CMakeLists.txt | 5 +++++ .../avro/cdc => protocol/examples/cdc.py} | 0 .../examples/cdc_kafka_producer.py} | 0 .../examples/cdc_last_transaction.py} | 0 .../avro => protocol/examples}/cdc_schema.go | 0 .../examples/cdc_users.py} | 0 server/modules/routing/avro/CMakeLists.txt | 5 ----- 11 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 server/modules/protocol/examples/CMakeLists.txt rename server/modules/{routing/avro/cdc => protocol/examples/cdc.py} (100%) rename server/modules/{routing/avro/cdc_kafka_producer => protocol/examples/cdc_kafka_producer.py} (100%) rename server/modules/{routing/avro/cdc_last_transaction => protocol/examples/cdc_last_transaction.py} (100%) rename server/modules/{routing/avro => protocol/examples}/cdc_schema.go (100%) rename server/modules/{routing/avro/cdc_users => protocol/examples/cdc_users.py} (100%) diff --git a/Documentation/Protocols/CDC.md b/Documentation/Protocols/CDC.md index 45cbe26f4..a20cf0e1c 100644 --- a/Documentation/Protocols/CDC.md +++ b/Documentation/Protocols/CDC.md @@ -125,3 +125,8 @@ Example: ``` QUERY-TRANSACTION 0-14-1245 ``` + +## Example Client + +MaxScale includes an example CDC client application written in Python 3. You can +find the source code for it [in the MaxScale repository](https://github.com/mariadb-corporation/MaxScale/tree/2.0/server/modules/protocol/examples/cdc.py). diff --git a/Documentation/Protocols/CDC_users.md b/Documentation/Protocols/CDC_users.md index bd9b25eec..2272885dd 100644 --- a/Documentation/Protocols/CDC_users.md +++ b/Documentation/Protocols/CDC_users.md @@ -21,16 +21,15 @@ password=cdc_password ## Creating new CDC users ``` -bash$ cdc_users [-h] USER PASSWORD +bash$ cdc_users.py [-h] USER PASSWORD ``` The output of this command should be appended to the _cdcusers_ file at `/var/lib/maxscale//`. ``` -bash$ cdc_users user1 pass1 >> /var/lib/maxscale/avro-service/cdcusers +bash$ cdc_users.py user1 pass1 >> /var/lib/maxscale/avro-service/cdcusers ``` Users can be deleted by removing the related rows in 'cdcusers' file. For more details on the format of the _cdcusers_ file, read the [CDC Protocol documentation](CDC.md). - diff --git a/Documentation/Routers/Avrorouter.md b/Documentation/Routers/Avrorouter.md index bc79731f7..f9e376ab7 100644 --- a/Documentation/Routers/Avrorouter.md +++ b/Documentation/Routers/Avrorouter.md @@ -152,16 +152,16 @@ conversion process, delete these two files and restart MaxScale. # Example Client -The avrorouter comes with an example client program, _cdc_, written in Python 3. +The avrorouter comes with an example client program, _cdc.py_, written in Python 3. This client can connect to a MaxScale configured with the CDC protocol and the avrorouter. Before using this client, you will need to install the Python 3 interpreter and -add users to the service with the _cdc_users_ script. Fore more details about +add users to the service with the _cdc_users.py_ script. Fore more details about the user creation, please refer to the [CDC Protocol](../Protocols/CDC.md) and [CDC Users](../Protocols/CDC_users.md) documentation. -Read the output of `cdc --help` for a full list of supported options +Read the output of `cdc.py --help` for a full list of supported options and a short usage description of the client program. # Avro Schema Generator @@ -206,27 +206,27 @@ protocol=CDC port=4001 ``` -Here is an example how you can query for data in JSON format using the _cdc_ Python script. +Here is an example how you can query for data in JSON format using the _cdc.py_ Python script. It queries the table _test.mytable_ for all change records. ``` -cdc --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable +cdc.py --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable ``` -You can then combine it with the _cdc_kafka_producer_ to publish these change records to a Kafka broker. +You can then combine it with the _cdc_kafka_producer.py_ to publish these change records to a Kafka broker. ``` -cdc --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable | cdc_kafka_producer --kafka-broker 127.0.0.1:9092 --kafka-topic test.mytable +cdc.py --user=myuser --password=mypasswd --host=127.0.0.1 --port=4001 test.mytable | cdc_kafka_producer.py --kafka-broker 127.0.0.1:9092 --kafka-topic test.mytable ``` -For more information on how to use these scripts, see the output of `cdc -h` and `cdc_kafka_producer -h`. +For more information on how to use these scripts, see the output of `cdc.py -h` and `cdc_kafka_producer.py -h`. # Building Avrorouter To build the avrorouter from source, you will need the [Avro C](https://avro.apache.org/docs/current/api/c/) -library, liblzma and sqlite3 development headers. When configuring MaxScale with -CMake, you will need to add `-DBUILD_AVRO=Y -DBUILD_CDC=Y` to build the -avrorouter and the CDC protocol module. +library, liblzma, [the Jansson library](http://www.digip.org/jansson/) and sqlite3 development headers. When +configuring MaxScale with CMake, you will need to add `-DBUILD_AVRO=Y +-DBUILD_CDC=Y` to build the avrorouter and the CDC protocol module. For more details about building MaxScale from source, please refer to the [Building MaxScale from Source Code](../Getting-Started/Building-MaxScale-from-Source-Code.md) document. diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index ff8f765ec..e7da598bd 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -34,4 +34,5 @@ if(BUILD_CDC) target_link_libraries(CDC maxscale-common) set_target_properties(CDC PROPERTIES VERSION "1.0.1") install(TARGETS CDC DESTINATION ${MAXSCALE_LIBDIR}) + add_subdirectory(examples) endif() diff --git a/server/modules/protocol/examples/CMakeLists.txt b/server/modules/protocol/examples/CMakeLists.txt new file mode 100644 index 000000000..63ffb2ed4 --- /dev/null +++ b/server/modules/protocol/examples/CMakeLists.txt @@ -0,0 +1,5 @@ +install(PROGRAMS cdc.py DESTINATION ${MAXSCALE_BINDIR}) +install(PROGRAMS cdc_users.py DESTINATION ${MAXSCALE_BINDIR}) +install(PROGRAMS cdc_last_transaction.py DESTINATION ${MAXSCALE_BINDIR}) +install(PROGRAMS cdc_kafka_producer.py DESTINATION ${MAXSCALE_BINDIR}) +install(FILES cdc_schema.go DESTINATION ${MAXSCALE_SHAREDIR}) diff --git a/server/modules/routing/avro/cdc b/server/modules/protocol/examples/cdc.py similarity index 100% rename from server/modules/routing/avro/cdc rename to server/modules/protocol/examples/cdc.py diff --git a/server/modules/routing/avro/cdc_kafka_producer b/server/modules/protocol/examples/cdc_kafka_producer.py similarity index 100% rename from server/modules/routing/avro/cdc_kafka_producer rename to server/modules/protocol/examples/cdc_kafka_producer.py diff --git a/server/modules/routing/avro/cdc_last_transaction b/server/modules/protocol/examples/cdc_last_transaction.py similarity index 100% rename from server/modules/routing/avro/cdc_last_transaction rename to server/modules/protocol/examples/cdc_last_transaction.py diff --git a/server/modules/routing/avro/cdc_schema.go b/server/modules/protocol/examples/cdc_schema.go similarity index 100% rename from server/modules/routing/avro/cdc_schema.go rename to server/modules/protocol/examples/cdc_schema.go diff --git a/server/modules/routing/avro/cdc_users b/server/modules/protocol/examples/cdc_users.py similarity index 100% rename from server/modules/routing/avro/cdc_users rename to server/modules/protocol/examples/cdc_users.py diff --git a/server/modules/routing/avro/CMakeLists.txt b/server/modules/routing/avro/CMakeLists.txt index 379045ece..b793795f2 100644 --- a/server/modules/routing/avro/CMakeLists.txt +++ b/server/modules/routing/avro/CMakeLists.txt @@ -5,11 +5,6 @@ if(AVRO_FOUND) set_target_properties(avrorouter PROPERTIES LINK_FLAGS -Wl,-z,defs) target_link_libraries(avrorouter maxscale-common jansson ${AVRO_LIBRARIES} maxavro sqlite3 lzma) install(TARGETS avrorouter DESTINATION ${MAXSCALE_LIBDIR}) - install(PROGRAMS cdc DESTINATION ${MAXSCALE_BINDIR}) - install(PROGRAMS cdc_users DESTINATION ${MAXSCALE_BINDIR}) - install(PROGRAMS cdc_last_transaction DESTINATION ${MAXSCALE_BINDIR}) - install(PROGRAMS cdc_kafka_producer DESTINATION ${MAXSCALE_BINDIR}) - install(FILES cdc_schema.go DESTINATION ${MAXSCALE_SHAREDIR}) else() message(STATUS "Avro C libraries were not found, avrorouter will not be built.") endif() From ca6c619d6054e7f43075502630c9029dffbbf6f7 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 6 Sep 2016 10:14:22 +0300 Subject: [PATCH 13/36] Do not access uninitialized file object If the opening of the logfile fails it must not be assumed to have been opened when cleaning up. --- server/core/log_manager.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/core/log_manager.cc b/server/core/log_manager.cc index 097ba88c5..f8c85eca3 100644 --- a/server/core/log_manager.cc +++ b/server/core/log_manager.cc @@ -2052,9 +2052,6 @@ static void filewriter_done(filewriter_t* fw) { case RUN: CHK_FILEWRITER(fw); - case INIT: - fw->fwr_logmes = NULL; - fw->fwr_clientmes = NULL; if (log_config.use_stdout) { skygw_file_free(fw->fwr_file); @@ -2063,7 +2060,11 @@ static void filewriter_done(filewriter_t* fw) { skygw_file_close(fw->fwr_file, true); } + case INIT: + fw->fwr_logmes = NULL; + fw->fwr_clientmes = NULL; fw->fwr_state = DONE; + break; case DONE: case UNINIT: default: From 0aec0c483c22de5f91dec77e59564755e5536adb Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 6 Sep 2016 10:50:13 +0300 Subject: [PATCH 14/36] Cleanup early error logging If the log file could not be opened, it was reported over and over and over again to stderr. --- server/core/gateway.c | 29 +++++++++++++---------------- server/core/log_manager.cc | 9 +++------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/server/core/gateway.c b/server/core/gateway.c index 168251857..ad75dbb0f 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -693,30 +693,27 @@ static void print_log_n_stderr( const char* fprstr, /*< string to be printed to stderr */ int eno) /*< errno, if it is set, zero, otherwise */ { - char* log_err = "Error :"; - char* fpr_err = "*\n* Error :"; - char* fpr_end = "\n*\n"; - if (do_log) { - mxs_log_init(NULL, get_logdir(), MXS_LOG_TARGET_FS); - char errbuf[STRERROR_BUFLEN]; - MXS_ERROR("%s %s %s %s", - log_err, - logstr, - eno == 0 ? " " : "Error :", - eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf))); + if (mxs_log_init(NULL, get_logdir(), MXS_LOG_TARGET_FS)) + { + char errbuf[STRERROR_BUFLEN]; + MXS_ERROR("%s%s%s%s", + logstr, + eno == 0 ? "" : " (", + eno == 0 ? "" : strerror_r(eno, errbuf, sizeof(errbuf)), + eno == 0 ? "" : ")"); + } } if (do_stderr) { char errbuf[STRERROR_BUFLEN]; fprintf(stderr, - "%s %s %s %s %s", - fpr_err, + "* Error: %s%s%s%s\n", fprstr, - eno == 0 ? " " : "Error :", - eno == 0 ? " " : strerror_r(eno, errbuf, sizeof(errbuf)), - fpr_end); + eno == 0 ? "" : " (", + eno == 0 ? "" : strerror_r(eno, errbuf, sizeof(errbuf)), + eno == 0 ? "" : ")"); } } diff --git a/server/core/log_manager.cc b/server/core/log_manager.cc index f8c85eca3..e6e07ed99 100644 --- a/server/core/log_manager.cc +++ b/server/core/log_manager.cc @@ -443,7 +443,7 @@ return_succ: { /** This releases memory of all created objects */ logmanager_done_nomutex(); - fprintf(stderr, "*\n* Error : Initializing log manager failed.\n*\n"); + fprintf(stderr, "* Error: Initializing the log manager failed.\n"); } return succ; } @@ -1566,7 +1566,7 @@ static bool logfile_open_file(filewriter_t* fw, logfile_t* lf) if (fw->fwr_file == NULL) { - fprintf(stderr, "Error : opening logfile %s failed.\n", lf->lf_full_file_name); + // Error logged by skygw_file_init to stderr. rv = false; } @@ -2035,10 +2035,7 @@ static bool filewriter_init(logmanager_t* logmanager, filewriter_t* fw) } else { - fprintf(stderr, - "Error : opening log file %s failed. Exiting " - "MaxScale\n", - lf->lf_full_file_name); + // Error logged already to stderr. filewriter_done(fw); } From 3e08c248b988ca3d425cc225f6779efdfc959546 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 6 Sep 2016 05:10:52 +0300 Subject: [PATCH 15/36] Fix maxinfo hang dcb_count_by_usage did not iterate the list properly and would get stuck on the first inactive DCB. Since this function is only called by maxinfo, it would be the only one to get stuck. --- server/core/dcb.c | 68 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 43b47ae8f..3e115d4f6 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2970,46 +2970,46 @@ dcb_count_by_usage(DCB_USAGE usage) { if (dcb->dcb_is_in_use) { - switch (usage) - { - case DCB_USAGE_CLIENT: - if (DCB_ROLE_CLIENT_HANDLER == dcb->dcb_role) + switch (usage) { + case DCB_USAGE_CLIENT: + if (DCB_ROLE_CLIENT_HANDLER == dcb->dcb_role) + { + rval++; + } + break; + case DCB_USAGE_LISTENER: + if (dcb->state == DCB_STATE_LISTENING) + { + rval++; + } + break; + case DCB_USAGE_BACKEND: + if (dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER) + { + rval++; + } + break; + case DCB_USAGE_INTERNAL: + if (dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER || + dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER) + { + rval++; + } + break; + case DCB_USAGE_ZOMBIE: + if (DCB_ISZOMBIE(dcb)) + { + rval++; + } + break; + case DCB_USAGE_ALL: rval++; + break; } - break; - case DCB_USAGE_LISTENER: - if (dcb->state == DCB_STATE_LISTENING) - { - rval++; - } - break; - case DCB_USAGE_BACKEND: - if (dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER) - { - rval++; - } - break; - case DCB_USAGE_INTERNAL: - if (dcb->dcb_role == DCB_ROLE_CLIENT_HANDLER || - dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER) - { - rval++; - } - break; - case DCB_USAGE_ZOMBIE: - if (DCB_ISZOMBIE(dcb)) - { - rval++; - } - break; - case DCB_USAGE_ALL: - rval++; - break; } dcb = dcb->next; } - } spinlock_release(&dcbspin); return rval; } From fca09e0d7b4a4a8f859c3830f11659cb8eafaf8d Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Tue, 6 Sep 2016 15:05:34 +0300 Subject: [PATCH 16/36] MXS-836: Fix retry_on_failure not working The service start retry mechanism mistakenly returned an error when a service failed to start but a retry was queued. This caused MaxScale to stop whenever a service failed to start. --- server/core/config.c | 1 + server/core/service.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/server/core/config.c b/server/core/config.c index 2559fec02..2ca6dcdab 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -135,6 +135,7 @@ static char *service_params[] = "ignore_databases_regex", "log_auth_warnings", "source", /**< Avrorouter only */ + "retry_on_failure", NULL }; diff --git a/server/core/service.c b/server/core/service.c index ddb9ec99b..1aa106eb3 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -452,6 +452,9 @@ int serviceStartAllPorts(SERVICE* service) (void*) service, retry_after); MXS_NOTICE("Failed to start service %s, retrying in %d seconds.", service->name, retry_after); + + /** This will prevent MaxScale from shutting down if service start is retried later */ + listeners = 1; } } else From a4903cff7340bf3597dea25e7c1c384cffe051ba Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 7 Sep 2016 09:20:45 +0300 Subject: [PATCH 17/36] Accept 'password' in addition to 'passwd' In the configuration section of services and monitors, the password to be used can now be specified using 'password' in addition to 'passwd'. If both are provided, then the value of 'passwd' is used. That way there cannot be any surprises, should someone for whatever reason currently (in 1.4.3 an invalid parameter will not prevent MaxScale from starting) have a 'password' entry in his config file. In the next release 'passwd' can be deprecated and in the release after that removed. --- .../MaxScale-2.0.1-Release-Notes.md | 11 ++++++ server/core/config.c | 39 ++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md index 9f7b538bd..3ccb0cba3 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -13,6 +13,17 @@ report at [Jira](https://jira.mariadb.org). ## Updated Features +### Password parameter + +In the configuration entry for a _service_ or _monitor_, the value of +the password to be used can now be specified using `password` in addition +to `passwd`. The use of the latter will be deprecated and removed in later +releases of MaxScale. + + [SomeService] + ... + password=mypasswd + ### Routing hint priority change Routing hints now have the highest priority when a routing decision is made. If diff --git a/server/core/config.c b/server/core/config.c index 2ca6dcdab..6e6b517d9 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -80,6 +80,7 @@ static bool process_config_context(CONFIG_CONTEXT *); static int process_config_update(CONFIG_CONTEXT *); static void free_config_context(CONFIG_CONTEXT *); static char *config_get_value(CONFIG_PARAMETER *, const char *); +static char *config_get_password(CONFIG_PARAMETER *); static const char *config_get_value_string(CONFIG_PARAMETER *, const char *); static int handle_global_item(const char *, const char *); static int handle_feedback_item(const char *, const char *); @@ -115,7 +116,8 @@ static char *service_params[] = "router_options", "servers", "user", - "passwd", + "passwd", // DEPRECATE: See config_get_password. + "password", "enable_root_user", "max_connections", /* "max_queued_connections", */ @@ -163,7 +165,8 @@ static char *monitor_params[] = "module", "servers", "user", - "passwd", + "passwd", // DEPRECATE: See config_get_password. + "password", "script", "events", "mysql51_replication", @@ -592,6 +595,30 @@ config_get_value(CONFIG_PARAMETER *params, const char *name) return NULL; } +// DEPRECATE: In 2.1 complain but accept if "passwd" is provided, in 2.2 +// DEPRECATE: drop support for "passwd". +/** + * Get the value of the password parameter + * + * The words looked for are "password" and "passwd". + * + * @param params The linked list of config parameters + * @return the parameter value or NULL if not found + */ +static char * +config_get_password(CONFIG_PARAMETER *params) +{ + char *password = config_get_value(params, "password"); + char *passwd = config_get_value(params, "passwd"); + + if (password && passwd) + { + MXS_WARNING("Both 'password' and 'passwd' specified. Using value of 'password'."); + } + + return passwd ? passwd : password; +} + /** * Get the value of a config parameter as a string * @@ -1310,7 +1337,7 @@ process_config_update(CONFIG_CONTEXT *context) max_queued_connections = config_get_value_string(obj->parameters, "max_queued_connections"); queued_connection_timeout = config_get_value_string(obj->parameters, "queued_connection_timeout"); user = config_get_value(obj->parameters, "user"); - auth = config_get_value(obj->parameters, "passwd"); + auth = config_get_password(obj->parameters); auth_all_servers = config_get_value(obj->parameters, "auth_all_servers"); strip_db_esc = config_get_value(obj->parameters, "strip_db_esc"); @@ -2243,7 +2270,7 @@ int create_new_service(CONFIG_CONTEXT *obj) } char *user = config_get_value(obj->parameters, "user"); - char *auth = config_get_value(obj->parameters, "passwd"); + char *auth = config_get_password(obj->parameters); if (user && auth) { @@ -2256,7 +2283,7 @@ int create_new_service(CONFIG_CONTEXT *obj) obj->object, user ? "" : "the 'user' parameter", !user && !auth ? " and " : "", - auth ? "" : "the 'passwd' parameter"); + auth ? "" : "the 'password' or 'passwd' parameter"); } char *subservices = config_get_value(obj->parameters, "subservices"); @@ -2660,7 +2687,7 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* } char *user = config_get_value(obj->parameters, "user"); - char *passwd = config_get_value(obj->parameters, "passwd"); + char *passwd = config_get_password(obj->parameters); if (user && passwd) { monitorAddUser(obj->element, user, passwd); From 4940e1482fcaae9142ea88f8d9c4916e24811652 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 8 Sep 2016 08:52:14 +0200 Subject: [PATCH 18/36] Update Replication-Proxy-Binlog-Router-Tutorial.md Removed "servers=" in the example --- .../Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md index 0004b0b19..84d50d158 100644 --- a/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md +++ b/Documentation/Tutorials/Replication-Proxy-Binlog-Router-Tutorial.md @@ -133,7 +133,6 @@ A complete example of a service entry for a binlog router service would be as fo [Replication] type=service router=binlogrouter - servers=masterdb version_string=5.6.17-log user=maxscale passwd=Mhu87p2D From a474dad7539f55fdb218e88d5ddf2ce675869ab3 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 8 Sep 2016 09:46:46 +0300 Subject: [PATCH 19/36] Fix crash when multiple MySQL monitors monitor same servers The monitors always freed and reallocated the memory for the slaves. It was always of the same size so a static array of that size should also work. --- server/core/server.c | 1 - server/include/monitor.h | 1 - server/include/server.h | 3 ++- server/modules/monitor/mysql_mon.c | 14 +++++--------- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/server/core/server.c b/server/core/server.c index e39c605a8..4de4e4d64 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -81,7 +81,6 @@ server_alloc(char *servname, char *protocol, unsigned short port) server->rlag = -2; server->master_id = -1; server->depth = -1; - server->slaves = NULL; server->parameters = NULL; server->server_string = NULL; spinlock_init(&server->lock); diff --git a/server/include/monitor.h b/server/include/monitor.h index 00f6457c2..97f717958 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -129,7 +129,6 @@ typedef enum #define MONITOR_INTERVAL 10000 // in milliseconds #define MONITOR_DEFAULT_ID 1UL // unsigned long value -#define MONITOR_MAX_NUM_SLAVES 20 //number of MySQL slave servers associated to a MySQL master server /* * Create declarations of the enum for monitor events and also the array of diff --git a/server/include/server.h b/server/include/server.h index d8b3e1af5..81a6a2313 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -45,6 +45,7 @@ */ #define MAX_SERVER_NAME_LEN 1024 +#define MAX_NUM_SLAVES 128 /**< Maximum number of slaves under a single server*/ /** * The server parameters used for weighting routing decissions @@ -99,7 +100,7 @@ typedef struct server SERVER_PARAM *parameters; /**< Parameters of a server that may be used to weight routing decisions */ long master_id; /**< Master server id of this node */ int depth; /**< Replication level in the tree */ - long *slaves; /**< Slaves of this node */ + long slaves[MAX_NUM_SLAVES]; /**< Slaves of this node */ bool master_err_is_logged; /*< If node failed, this indicates whether it is logged */ DCB *persistent; /**< List of unused persistent connections to the server */ SPINLOCK persistlock; /**< Lock for adjusting the persistent connections list */ diff --git a/server/modules/monitor/mysql_mon.c b/server/modules/monitor/mysql_mon.c index d2f8b099c..6948175c1 100644 --- a/server/modules/monitor/mysql_mon.c +++ b/server/modules/monitor/mysql_mon.c @@ -550,7 +550,7 @@ static MONITOR_SERVERS *build_mysql51_replication_tree(MONITOR *mon) if (mysql_num_rows(result) > 0) { ismaster = true; - while (nslaves < MONITOR_MAX_NUM_SLAVES && (row = mysql_fetch_row(result))) + while (nslaves < MAX_NUM_SLAVES && (row = mysql_fetch_row(result))) { /* get Slave_IO_Running and Slave_SQL_Running values*/ database->server->slaves[nslaves] = atol(row[0]); @@ -838,12 +838,7 @@ monitorMain(void *arg) monitorDatabase(mon, ptr); /* reset the slave list of current node */ - if (ptr->server->slaves) - { - free(ptr->server->slaves); - } - /* create a new slave list */ - ptr->server->slaves = (long *) calloc(MONITOR_MAX_NUM_SLAVES, sizeof(long)); + memset(&ptr->server->slaves, 0, sizeof(ptr->server->slaves)); num_servers++; @@ -1450,7 +1445,8 @@ static MONITOR_SERVERS *get_replication_tree(MONITOR *mon, int num_servers) master = getServerByNodeId(mon->databases, current->master_id); if (master && master->server && master->server->node_id > 0) { - add_slave_to_master(master->server->slaves, MONITOR_MAX_NUM_SLAVES, current->node_id); + add_slave_to_master(master->server->slaves, sizeof(master->server->slaves), + current->node_id); master->server->depth = current->depth - 1; monitor_set_pending_status(master, SERVER_MASTER); handle->master = master; @@ -1511,7 +1507,7 @@ static int add_slave_to_master(long *slaves_list, int list_size, long node_id) { if (slaves_list[i] == 0) { - memcpy(&slaves_list[i], &node_id, sizeof(long)); + slaves_list[i] = node_id; return 1; } } From a074605c585d33c889b3c3dd5c1aac1c8c1725d3 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 8 Sep 2016 13:01:40 +0300 Subject: [PATCH 20/36] MXS-825: Add support for --execdir Although claimed in the output of "--help", the long option "--execdir" was not supported. Support for that now added. The long options have now also been sorted in the same order as the options are displayed by the help, to make it easy to check that everything is there. Further, the description column of the output of --help has been aligned. --- server/core/gateway.c | 69 ++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/server/core/gateway.c b/server/core/gateway.c index ad75dbb0f..0c632320e 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -124,23 +124,24 @@ const char *progname = NULL; static struct option long_options[] = { {"config-check", no_argument, 0, 'c'}, - {"config", required_argument, 0, 'f'}, {"nodaemon", no_argument, 0, 'd'}, + {"config", required_argument, 0, 'f'}, {"log", required_argument, 0, 'l'}, {"logdir", required_argument, 0, 'L'}, - {"datadir", required_argument, 0, 'D'}, - {"configdir", required_argument, 0, 'C'}, - {"piddir", required_argument, 0, 'P'}, - {"libdir", required_argument, 0, 'B'}, {"cachedir", required_argument, 0, 'A'}, + {"libdir", required_argument, 0, 'B'}, + {"configdir", required_argument, 0, 'C'}, + {"datadir", required_argument, 0, 'D'}, + {"execdir", required_argument, 0, 'E'}, {"language", required_argument, 0, 'N'}, - {"syslog", required_argument, 0, 's'}, - {"maxlog", required_argument, 0, 'S'}, + {"piddir", required_argument, 0, 'P'}, {"user", required_argument, 0, 'U'}, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, '?'}, - {"version-full", no_argument, 0, 'V'}, + {"syslog", required_argument, 0, 's'}, + {"maxlog", required_argument, 0, 'S'}, {"log_augmentation", required_argument, 0, 'G'}, + {"version", no_argument, 0, 'v'}, + {"version-full", no_argument, 0, 'V'}, + {"help", no_argument, 0, '?'}, {0, 0, 0, 0} }; static bool syslog_configured = false; @@ -874,31 +875,31 @@ static void usage(void) { fprintf(stderr, "\nUsage : %s [OPTION]...\n\n" - " -c, --config-check Validate configuration file and exit\n" - " -d, --nodaemon enable running in terminal process (default:disabled)\n" - " -f, --config=FILE relative or absolute pathname of MaxScale configuration file\n" - " (default:/etc/maxscale.cnf)\n" + " -c, --config-check Validate configuration file and exit\n" + " -d, --nodaemon enable running in terminal process (default:disabled)\n" + " -f, --config=FILE relative or absolute pathname of MaxScale configuration file\n" + " (default:/etc/maxscale.cnf)\n" " -l, --log=[file|shm|stdout] log to file, shared memory or stdout (default: file)\n" - " -L, --logdir=PATH path to log file directory (default: /var/log/maxscale)\n" - " -A, --cachedir=PATH path to cache directory (default: /var/cache/maxscale)\n" - " -B, --libdir=PATH path to module directory (default: /usr/lib64/maxscale)\n" - " -C, --configdir=PATH path to configuration file directory (default: /etc/)\n" - " -D, --datadir=PATH path to data directory, stored embedded mysql tables\n" - " (default: /var/cache/maxscale)\n" - " -E, --execdir=PATH path to the maxscale and other executable files\n" - " (default: /usr/bin)\n" - " -N, --language=PATH path to errmsg.sys file (default: /var/lib/maxscale)\n" - " -P, --piddir=PATH path to PID file directory (default: /var/run/maxscale)\n" - " -U, --user=USER run MaxScale as another user.\n" - " The user ID and group ID of this user are used to run MaxScale.\n" - " -s, --syslog=[yes|no] log messages to syslog (default:yes)\n" - " -S, --maxlog=[yes|no] log messages to MaxScale log (default: yes)\n" - " -G, --log_augmentation=0|1 augment messages with the name of the function where\n" - " the message was logged (default: 0). Primarily for \n" - " development purposes.\n" - " -v, --version print version info and exit\n" - " -V, --version-full print full version info and exit\n" - " -?, --help show this help\n" + " -L, --logdir=PATH path to log file directory (default: /var/log/maxscale)\n" + " -A, --cachedir=PATH path to cache directory (default: /var/cache/maxscale)\n" + " -B, --libdir=PATH path to module directory (default: /usr/lib64/maxscale)\n" + " -C, --configdir=PATH path to configuration file directory (default: /etc/)\n" + " -D, --datadir=PATH path to data directory, stored embedded mysql tables\n" + " (default: /var/cache/maxscale)\n" + " -E, --execdir=PATH path to the maxscale and other executable files\n" + " (default: /usr/bin)\n" + " -N, --language=PATH path to errmsg.sys file (default: /var/lib/maxscale)\n" + " -P, --piddir=PATH path to PID file directory (default: /var/run/maxscale)\n" + " -U, --user=USER run MaxScale as another user.\n" + " The user ID and group ID of this user are used to run MaxScale.\n" + " -s, --syslog=[yes|no] log messages to syslog (default:yes)\n" + " -S, --maxlog=[yes|no] log messages to MaxScale log (default: yes)\n" + " -G, --log_augmentation=0|1 augment messages with the name of the function where\n" + " the message was logged (default: 0). Primarily for \n" + " development purposes.\n" + " -v, --version print version info and exit\n" + " -V, --version-full print full version info and exit\n" + " -?, --help show this help\n" , progname); } From f798bc9f64d1ee003ee33f43187f22b56cf7746c Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 8 Sep 2016 14:30:10 +0300 Subject: [PATCH 21/36] Print correct default directories Invoking 'maxscale --help' now displays the correct default directories. --- server/core/gateway.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/server/core/gateway.c b/server/core/gateway.c index 0c632320e..2ef294886 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -880,16 +880,14 @@ static void usage(void) " -f, --config=FILE relative or absolute pathname of MaxScale configuration file\n" " (default:/etc/maxscale.cnf)\n" " -l, --log=[file|shm|stdout] log to file, shared memory or stdout (default: file)\n" - " -L, --logdir=PATH path to log file directory (default: /var/log/maxscale)\n" - " -A, --cachedir=PATH path to cache directory (default: /var/cache/maxscale)\n" - " -B, --libdir=PATH path to module directory (default: /usr/lib64/maxscale)\n" - " -C, --configdir=PATH path to configuration file directory (default: /etc/)\n" + " -L, --logdir=PATH path to log file directory\n" + " -A, --cachedir=PATH path to cache directory\n" + " -B, --libdir=PATH path to module directory\n" + " -C, --configdir=PATH path to configuration file directory\n" " -D, --datadir=PATH path to data directory, stored embedded mysql tables\n" - " (default: /var/cache/maxscale)\n" " -E, --execdir=PATH path to the maxscale and other executable files\n" - " (default: /usr/bin)\n" - " -N, --language=PATH path to errmsg.sys file (default: /var/lib/maxscale)\n" - " -P, --piddir=PATH path to PID file directory (default: /var/run/maxscale)\n" + " -N, --language=PATH path to errmsg.sys file\n" + " -P, --piddir=PATH path to PID file directory\n" " -U, --user=USER run MaxScale as another user.\n" " The user ID and group ID of this user are used to run MaxScale.\n" " -s, --syslog=[yes|no] log messages to syslog (default:yes)\n" @@ -900,7 +898,19 @@ static void usage(void) " -v, --version print version info and exit\n" " -V, --version-full print full version info and exit\n" " -?, --help show this help\n" - , progname); + "\n" + "Defaults:\n" + " logdir : %s\n" + " cachedir : %s\n" + " libdir : %s\n" + " configdir: %s\n" + " datadir : %s\n" + " execdir : %s\n" + " language : %s\n" + " piddir : %s\n" + , progname + , get_logdir(), get_cachedir(), get_libdir(), get_configdir() + , get_datadir(), get_execdir(), get_langdir(), get_piddir()); } From 60c61157cc27bca6163e05333d11f2c6f30784f2 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 8 Sep 2016 10:02:01 +0300 Subject: [PATCH 22/36] Update limitations document The document is now split into module type sections. Added documentation on the limitations on multiple monitors monitoring the same servers and filters not receiving complete packets when used with readconnroute. --- Documentation/About/Limitations.md | 59 +++++++++++++++++++----------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/Documentation/About/Limitations.md b/Documentation/About/Limitations.md index 4b59536d2..87f75101f 100644 --- a/Documentation/About/Limitations.md +++ b/Documentation/About/Limitations.md @@ -7,12 +7,12 @@ to specific plugins or to MariaDB MaxScale as a whole this document is divided into a number of sections, the purpose of which are to isolate the limitations to the components which illustrate them. -## Limitations in the MariaDB MaxScale core +# Limitations in the MariaDB MaxScale core This section describes the limitations that are common to all configuration of plugins with MariaDB MaxScale. -### Crash if one of several listeners for a Service fails as startup +## Crash if one of several listeners for a Service fails as startup If a service has multiple listeners and one of those listeners fails at startup, MariaDB MaxScale will crash. @@ -26,16 +26,25 @@ Workaround: Ensure that socket paths and ports are valid. Issues [MXS-710](https://jira.mariadb.org/browse/MXS-710) and [MXS-711](https://jira.mariadb.org/browse/MXS-711) relate to this. +# Protocol limitations + ## Limitations with MySQL Protocol support (MySQLClient) Compression is not included in MySQL server handshake +# Monitor limitations + +A server can only be monitored by one monitor. If multiple monitors monitor the +same server, the state of the server is non-deterministic. + ## Limitations with Galera Cluster Monitoring (galeramon) The default master selection is based only on MIN(wsrep_local_index). This can be influenced with the server priority mechanic described in the [Galera Monitor](../Monitors/Galera-Monitor.md) manual. +# Router limitations + ## Limitations in the connection router (readconnroute) * If Master changes (ie. new Master promotion) during current connection @@ -191,20 +200,6 @@ possible that a slave fails to execute something because of some non-fatal, temporary failure, while the execution of the same command succeeds in other backends. -## Authentication Related Limitations (MySQLAuth) - -* MariaDB MaxScale can not manage authentication that uses wildcard matching in hostnames - in the mysql.user table of the backend database. The only wildcards that can be - used are in IP address entries. - -* MySQL old style passwords are not supported. MySQL versions 4.1 and newer use - a new authentication protocol which does not support pre-4.1 style passwords. - -* When users have different passwords based on the host from which they connect - MariaDB MaxScale is unable to determine which password it should use to connect to the - backend database. This results in failed connections and unusable usernames - in MariaDB MaxScale. - ## Schemarouter limitations (schemarouter) The schemarouter router currently has some limitations due to the nature of @@ -234,11 +229,6 @@ and routed. Here is a list of the current limitations. the query will be routed to the first available server. This possibly returns an error about database rights instead of a missing database. -## Database Firewall limitations (dbfwfilter) - -The Database Firewall filter does not support multi-statements. Using them -will result in an error being sent to the client. - ## Avrorouter limitations (avrorouter) The avrorouter does not support the following data types and conversions. @@ -249,3 +239,30 @@ The avrorouter does not support the following data types and conversions. The avrorouter does not do any crash recovery. This means that the avro files need to be truncated to valid block lengths before starting the avrorouter. + +# Authenticator limitations + +## MySQL Authentication Related Limitations (MySQLAuth) + +* MariaDB MaxScale can not manage authentication that uses wildcard matching in hostnames + in the mysql.user table of the backend database. The only wildcards that can be + used are in IP address entries. + +* MySQL old style passwords are not supported. MySQL versions 4.1 and newer use + a new authentication protocol which does not support pre-4.1 style passwords. + +* When users have different passwords based on the host from which they connect + MariaDB MaxScale is unable to determine which password it should use to connect to the + backend database. This results in failed connections and unusable usernames + in MariaDB MaxScale. + +# Filter limitations + +Filters are not guaranteed to receive complete MySQL packets if they are used +with the readconnroute router. This can be fixed by using the readwritesplit +router. + +## Database Firewall limitations (dbfwfilter) + +The Database Firewall filter does not support multi-statements. Using them +will result in an error being sent to the client. From a87a9c75e574174a05a1a5ad9c03822d33e7ffcc Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 9 Sep 2016 09:58:34 +0300 Subject: [PATCH 23/36] Add --basedir flag If maxscale is invoked with '--basedir=PATH', all directory paths and the configuration file to be defined relative to that path. --- .../MaxScale-2.0.1-Release-Notes.md | 22 +++ server/core/gateway.c | 128 ++++++++++++++---- server/include/gwdirs.h.in | 25 ++-- 3 files changed, 141 insertions(+), 34 deletions(-) diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md index 3ccb0cba3..8746eeda7 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -13,6 +13,28 @@ report at [Jira](https://jira.mariadb.org). ## Updated Features +### Starting MariaDB MaxScale + +There is now a new command line parameter `--basedir=PATH` that will +cause all directory paths and the location of the configuration file +to be defined relative to that path. + +For instance, invoking MariaDB MaxScale like + + $ maxscale --basedir=/path/maxscale + +has the same effect as invoking MariaDB MaxScale like + + $ maxscale --config=/path/maxscale/etc/maxscale.cnf + --configdir=/path/maxscale/etc + --logdir=/path/maxscale/var/log/maxscale + --cachhedir=/path/maxscale/var/cache/maxscale + --libdir=/path/maxscale/lib/maxscale + --datadir=/path/maxscale/var/lib/maxscale + --execdir=/path/maxscale/bin + --language=/path/maxscale/var/lib/maxscale + --piddir=/path/maxscale/var/run/maxscale + ### Password parameter In the configuration entry for a _service_ or _monitor_, the value of diff --git a/server/core/gateway.c b/server/core/gateway.c index 2ef294886..c1901f8c4 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -135,6 +135,7 @@ static struct option long_options[] = {"execdir", required_argument, 0, 'E'}, {"language", required_argument, 0, 'N'}, {"piddir", required_argument, 0, 'P'}, + {"basedir", required_argument, 0, 'R'}, {"user", required_argument, 0, 'U'}, {"syslog", required_argument, 0, 's'}, {"maxlog", required_argument, 0, 'S'}, @@ -162,7 +163,7 @@ static void write_footer(void); static int ntfw_cb(const char*, const struct stat*, int, struct FTW*); static bool file_is_readable(const char* absolute_pathname); static bool file_is_writable(const char* absolute_pathname); -bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr); +bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr); static void set_log_augmentation(const char* value); static void usage(void); static char* get_expanded_pathname( @@ -875,42 +876,52 @@ static void usage(void) { fprintf(stderr, "\nUsage : %s [OPTION]...\n\n" - " -c, --config-check Validate configuration file and exit\n" - " -d, --nodaemon enable running in terminal process (default:disabled)\n" - " -f, --config=FILE relative or absolute pathname of MaxScale configuration file\n" - " (default:/etc/maxscale.cnf)\n" - " -l, --log=[file|shm|stdout] log to file, shared memory or stdout (default: file)\n" + " -c, --config-check validate configuration file and exit\n" + " -d, --nodaemon enable running in terminal process\n" + " -f, --config=FILE relative or absolute pathname of config file\n" + " -l, --log=[file|shm|stdout] log to file, shared memory or stdout\n" + " (default: file)\n" " -L, --logdir=PATH path to log file directory\n" " -A, --cachedir=PATH path to cache directory\n" " -B, --libdir=PATH path to module directory\n" " -C, --configdir=PATH path to configuration file directory\n" - " -D, --datadir=PATH path to data directory, stored embedded mysql tables\n" + " -D, --datadir=PATH path to data directory,\n" + " stored embedded mysql tables\n" " -E, --execdir=PATH path to the maxscale and other executable files\n" " -N, --language=PATH path to errmsg.sys file\n" " -P, --piddir=PATH path to PID file directory\n" - " -U, --user=USER run MaxScale as another user.\n" - " The user ID and group ID of this user are used to run MaxScale.\n" + " -R, --basedir=PATH base path for all other paths\n" + " -U, --user=USER user ID and group ID of specified user are used to\n" + " run MaxScale\n" " -s, --syslog=[yes|no] log messages to syslog (default:yes)\n" " -S, --maxlog=[yes|no] log messages to MaxScale log (default: yes)\n" - " -G, --log_augmentation=0|1 augment messages with the name of the function where\n" - " the message was logged (default: 0). Primarily for \n" - " development purposes.\n" + " -G, --log_augmentation=0|1 augment messages with the name of the function\n" + " where the message was logged (default: 0)\n" " -v, --version print version info and exit\n" " -V, --version-full print full version info and exit\n" " -?, --help show this help\n" "\n" - "Defaults:\n" - " logdir : %s\n" - " cachedir : %s\n" - " libdir : %s\n" - " configdir: %s\n" - " datadir : %s\n" - " execdir : %s\n" - " language : %s\n" - " piddir : %s\n" - , progname - , get_logdir(), get_cachedir(), get_libdir(), get_configdir() - , get_datadir(), get_execdir(), get_langdir(), get_piddir()); + "Defaults paths:\n" + " config file: %s/%s\n" + " configdir : %s\n" + " logdir : %s\n" + " cachedir : %s\n" + " libdir : %s\n" + " datadir : %s\n" + " execdir : %s\n" + " language : %s\n" + " piddir : %s\n" + "\n" + "If '--basedir' is provided then all other paths, including the default\n" + "configuration file path, are defined relative to that. As an example,\n" + "if '--basedir /path/maxscale' is specified, then, for instance, the log\n" + "dir will be '/path/maxscale/var/log/maxscale', the config dir will be\n" + "'/path/maxscale/etc' and the default config file will be\n" + "'/path/maxscale/etc/maxscale.cnf'.\n", + progname, + get_configdir(), default_cnf_fname, + get_configdir(), get_logdir(), get_cachedir(), get_libdir(), + get_datadir(), get_execdir(), get_langdir(), get_piddir()); } @@ -1133,6 +1144,61 @@ bool configure_signals(void) return true; } +/** + * Set the directories of MaxScale relative to a basedir + * + * @param basedir The base directory relative to which the other are set. + * + * @return True if the directories could be set, false otherwise. + */ +bool set_dirs(const char *basedir) +{ + bool rv = true; + char *path; + + if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LOG_SUBPATH, true, false))) + { + set_logdir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_CACHE_SUBPATH, true, true))) + { + set_cachedir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_LIB_SUBPATH, true, false))) + { + set_libdir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_CONFIG_SUBPATH, true, false))) + { + set_configdir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_DATA_SUBPATH, true, false))) + { + set_datadir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, MXS_DEFAULT_EXEC_SUBPATH, true, false))) + { + set_execdir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_LANG_SUBPATH, true, false))) + { + set_langdir(path); + } + + if (rv && (rv = handle_path_arg(&path, basedir, "var/" MXS_DEFAULT_PID_SUBPATH, true, true))) + { + set_piddir(path); + } + + return rv; +} + /** * @mainpage * The main entry point into MaxScale @@ -1302,7 +1368,6 @@ int main(int argc, char **argv) } break; case 'L': - if (handle_path_arg(&tmp_path, optarg, NULL, true, false)) { set_logdir(tmp_path); @@ -1379,6 +1444,17 @@ int main(int argc, char **argv) succp = false; } break; + case 'R': + if (handle_path_arg(&tmp_path, optarg, NULL, true, false)) + { + succp = set_dirs(tmp_path); + free(tmp_path); + } + else + { + succp = false; + } + break; case 'S': { @@ -2252,7 +2328,7 @@ static int write_pid_file() return 0; } -bool handle_path_arg(char** dest, char* path, char* arg, bool rd, bool wr) +bool handle_path_arg(char** dest, const char* path, char* arg, bool rd, bool wr) { char pathbuffer[PATH_MAX + 2]; char* errstr; diff --git a/server/include/gwdirs.h.in b/server/include/gwdirs.h.in index 9501c5405..3c53d4cb6 100644 --- a/server/include/gwdirs.h.in +++ b/server/include/gwdirs.h.in @@ -21,19 +21,28 @@ EXTERN_C_BLOCK_BEGIN +#define MXS_DEFAULT_PID_SUBPATH "run/maxscale" +#define MXS_DEFAULT_LOG_SUBPATH "log/maxscale" +#define MXS_DEFAULT_DATA_SUBPATH "lib/maxscale" +#define MXS_DEFAULT_LIB_SUBPATH "@MAXSCALE_LIBDIR@" +#define MXS_DEFAULT_CACHE_SUBPATH "cache/maxscale" +#define MXS_DEFAULT_LANG_SUBPATH "lib/maxscale" +#define MXS_DEFAULT_EXEC_SUBPATH "@MAXSCALE_BINDIR@" +#define MXS_DEFAULT_CONFIG_SUBPATH "etc" + /** Default file locations, configured by CMake */ static const char* default_cnf_fname = "maxscale.cnf"; -static const char* default_configdir = "/etc"; +static const char* default_configdir = "/" MXS_DEFAULT_CONFIG_SUBPATH; /*< This should be changed to just /run eventually, * the /var/run folder is an old standard and the newer FSH 3.0 * uses /run for PID files.*/ -static const char* default_piddir = "@MAXSCALE_VARDIR@/run/maxscale"; -static const char* default_logdir = "@MAXSCALE_VARDIR@/log/maxscale"; -static const char* default_datadir = "@MAXSCALE_VARDIR@/lib/maxscale"; -static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_LIBDIR@"; -static const char* default_cachedir = "@MAXSCALE_VARDIR@/cache/maxscale"; -static const char* default_langdir = "@MAXSCALE_VARDIR@/lib/maxscale"; -static const char* default_execdir = "@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@"; +static const char* default_piddir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_PID_SUBPATH; +static const char* default_logdir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_LOG_SUBPATH; +static const char* default_datadir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_DATA_SUBPATH; +static const char* default_libdir = "@CMAKE_INSTALL_PREFIX@/" MXS_DEFAULT_LIB_SUBPATH; +static const char* default_cachedir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_CACHE_SUBPATH; +static const char* default_langdir = "@MAXSCALE_VARDIR@/" MXS_DEFAULT_LANG_SUBPATH; +static const char* default_execdir = "@CMAKE_INSTALL_PREFIX@/" MXS_DEFAULT_EXEC_SUBPATH; static char* configdir = NULL; static char* logdir = NULL; From b6d743fd03ac1ca6257dd86e54c31b5d97cb3525 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 9 Sep 2016 11:00:06 +0300 Subject: [PATCH 24/36] Create MaxScale directories If the installation directory is something else than /usr, then the directories /var/cache/maxscale /var/log/maxscale /var/run/maxscale will be created at installation time. --- CMakeLists.txt | 8 ++++++++ server/include/gwdirs.h.in | 2 ++ 2 files changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3762c85c..6da4daeea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,3 +357,11 @@ if(PACKAGE) message(STATUS "You can install startup scripts and system configuration files for MaxScale by running the 'postinst' shell script located at ${CMAKE_INSTALL_PREFIX}.") message(STATUS "To remove these installed files, run the 'postrm' shell script located in the same folder.") endif() + +# NOTE: If you make changes here, ensure they are compatible with the +# situation in gwdirs.h.in. +if (NOT CMAKE_INSTALL_PREFIX EQUAL "/usr") + install(DIRECTORY DESTINATION var/cache/maxscale) + install(DIRECTORY DESTINATION var/log/maxscale) + install(DIRECTORY DESTINATION var/run/maxscale) +endif() diff --git a/server/include/gwdirs.h.in b/server/include/gwdirs.h.in index 3c53d4cb6..0a2c0dab1 100644 --- a/server/include/gwdirs.h.in +++ b/server/include/gwdirs.h.in @@ -21,6 +21,8 @@ EXTERN_C_BLOCK_BEGIN +// NOTE: If you make changes here, ensure they are compatible with the +// situation in /CMakeLists.txt, where directories are installed. #define MXS_DEFAULT_PID_SUBPATH "run/maxscale" #define MXS_DEFAULT_LOG_SUBPATH "log/maxscale" #define MXS_DEFAULT_DATA_SUBPATH "lib/maxscale" From abd1ea53a62b2a0e1e561976cc4a5a2ce6dc930f Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 9 Sep 2016 11:42:14 +0300 Subject: [PATCH 25/36] Add tarball installation instructions --- ...nstall-MariaDB-MaxScale-Using-a-Tarball.md | 56 +++++++++++++++++++ .../MariaDB-MaxScale-Installation-Guide.md | 4 ++ 2 files changed, 60 insertions(+) create mode 100644 Documentation/Getting-Started/Install-MariaDB-MaxScale-Using-a-Tarball.md diff --git a/Documentation/Getting-Started/Install-MariaDB-MaxScale-Using-a-Tarball.md b/Documentation/Getting-Started/Install-MariaDB-MaxScale-Using-a-Tarball.md new file mode 100644 index 000000000..d9ceb1854 --- /dev/null +++ b/Documentation/Getting-Started/Install-MariaDB-MaxScale-Using-a-Tarball.md @@ -0,0 +1,56 @@ +# Installing MariaDB MaxScale using a tarball + +MariaDB MaxScale is also made available as a tarball, which is named like `maxscale-X.Y.X.tar.gz` where `X.Y.Z` is the same as the corresponding version, e.g. `maxscale-2.0.1.tar.gz`. + +The tarball has been built with the assumption that it will be installed in `/usr/local`. However, it is possible to install it in any directory, but in that case MariaDB MaxScale must be invoked with a flag. + +## Installing as root in `/usr/local` + +If you have root access to the system you probably want to install MariaDB MaxScale under the user and group `maxscale`. + +The required steps are as follows: + + $ sudo groupadd maxscale + $ sudo useradd -g maxscale maxscale + $ cd /usr/local + $ sudo tar -xzvf maxscale-X.Y.Z.tar.gz + $ sudo ln -s maxscale-X.Y.Z maxscale + $ cd maxscale + $ chown -R maxscale var + +Creating the symbolic link is necessary, since MariaDB MaxScale has been built with with the assumption that its base-directory is `/usr/local/maxscale`. It also makes it easy to switch between different versions of MariaDB MaxScale that have been installed side by side in `/usr/local`; just make the symbolic link point to another installation. + +The following step is to create the MariaDB MaxScale configuration file `/etc/maxscale.cnf`. The file `etc/maxscale.cnf.template` can be used as a base. Please refer to [Configuration Guide](Configuration-Guide.md) for details. + +When the configuration file has been created, MariaDB MaxScale can be started. + + $ sudo bin/maxscale --user=maxscale -d + +The `-d` flag causes maxscale _not_ to turn itself into a daemon, which is adviseable the first time MariaDB MaxScale is started, as it makes it easier to spot problems. + +If you want to place the configuration file somewhere else but in `/etc` you can invoke MariaDB MaxScale with the `--config` flag, for instance, `--config=/usr/local/maxscale/etc/maxscale.cnf`. + +## Installing in any Directory + +Enter a directory where you have the right to create a subdirectory. Then do as follows. + + $ tar -xzvf maxscale-X.Y.Z.tar.gz + +The next step is to create the MaxScale configuration file `maxscale-X.Y.Z/etc/maxscale.cnf`. The file `maxscale-X.Y.Z/etc/maxscale.cnf.template` can be used as a base. Please refer to [Configuration Guide](Configuration-Guide.md) for details. + +When the configuration file has been created, MariaDB MaxScale can be started. + + $ cd maxscale-X.Y.Z + $ LD_LIBRARY_PATH=lib/maxscale bin/maxscale -d --basedir=. + +With the flag `--basedir`, MariaDB MaxScale is told where the `bin`, `etc`, `lib` +and `var` directories are found. Unless it is specified, MariaDB MaxScale assumes +the directories are found in `/usr/local/maxscale` and the configuration +file in `/etc`. + +It is also possible to specify the directories and the location of the +configuration file individually. Invoke MaxScale like + + $ LD_LIBRARY_PATH=lib/maxscale bin/maxscale --help + +to find out the appropriate flags. diff --git a/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md b/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md index 4181f78f3..75f1412ae 100644 --- a/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md +++ b/Documentation/Getting-Started/MariaDB-MaxScale-Installation-Guide.md @@ -34,6 +34,10 @@ service maxscale start An example configuration file is installed into the `/etc/` folder. This file should be changed according to your needs. +## Install MariaDB MaxScale Using a Tarball + +MaxScale can also be installed using a tarball. That may be required if you are using a Linux distribution for which there exist no installation package or if you want to install many different MaxScale versions side by side. For instructions on how to do that, please refer to [Install MariaDB MaxScale using a Tarball](Install-MariaDB-MaxScale-Using-a-Tarball.md). + ## Building MariaDB MaxScale From Source Code Alternatively you may download the MariaDB MaxScale source and build your own binaries. To do this, refer to the separate document [Building MariaDB MaxScale from Source Code](Building-MaxScale-from-Source-Code.md) From 559f740910f1eefc73b9dc82e5ca01a76e040b8f Mon Sep 17 00:00:00 2001 From: Guillaume Lefranc Date: Fri, 9 Sep 2016 14:34:13 +0200 Subject: [PATCH 26/36] Clarify detect_stale_master defaults --- Documentation/Monitors/MySQL-Monitor.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/Monitors/MySQL-Monitor.md b/Documentation/Monitors/MySQL-Monitor.md index 808c1703d..e0941b561 100644 --- a/Documentation/Monitors/MySQL-Monitor.md +++ b/Documentation/Monitors/MySQL-Monitor.md @@ -55,7 +55,8 @@ and the table if they do not exist. Allow previous master to be available even in case of stopped or misconfigured replication. -This feature is enabled by default. +Starting from MaxScale 2.0.0 this feature is enabled by default. It is disabled +by default in MaxScale 1.4.3 and below. This allows services that depend on master and slave roles to continue functioning as long as the master server is available. This is a situation From f161c1e423382a1d270b862ff3e710984c42b133 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 9 Sep 2016 15:53:21 +0300 Subject: [PATCH 27/36] Add section about changed defaults to release notes A list of changes in the defaults is good to have in the release notes. --- Documentation/Getting-Started/Configuration-Guide.md | 8 ++++---- .../Release-Notes/MaxScale-2.0.1-Release-Notes.md | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index 8cd7c211a..5aa7a18d2 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -12,7 +12,7 @@ The purpose of this document is to describe how to configure MariaDB MaxScale an * [Server](#server) * [Server and SSL](#server-and-ssl) * [Listener](#listener) - * [Listener and SSL](#listener-and-ssl) + * [Listener and SSL](#listener-and-ssl) * [Router Modules](#routing-modules) * [Diagnostic Modules](#diagnostic-modules) * [Monitor Modules](#monitor-modules) @@ -662,7 +662,7 @@ For more information about persistent connections, please read the [Administrati ### Server and SSL -This section describes configuration parameters for servers that control the SSL/TLS encryption method and the various certificate files involved in it when applied to back end servers. To enable SSL between MaxScale and a back end server, you must configure the `ssl` parameter in the relevant server section to the value `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MaxScale connections to this server will be encrypted with SSL. Attempts to connect to the server without using SSL will cause failures. Hence, the database server in question must have been configured to be able to accept SSL connections. +This section describes configuration parameters for servers that control the SSL/TLS encryption method and the various certificate files involved in it when applied to back end servers. To enable SSL between MaxScale and a back end server, you must configure the `ssl` parameter in the relevant server section to the value `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MaxScale connections to this server will be encrypted with SSL. Attempts to connect to the server without using SSL will cause failures. Hence, the database server in question must have been configured to be able to accept SSL connections. #### `ssl` @@ -686,7 +686,7 @@ This parameter controls the level of encryption used. Accepted values are: * TLSv10 * TLSv11 * TLSv12 - * MAX + * MAX Not all backend servers will support TLSv11 or TLSv12. If available, TLSv12 should be used. @@ -805,7 +805,7 @@ This parameter controls the level of encryption used. Accepted values are: * TLSv10 * TLSv11 * TLSv12 - * MAX + * MAX If possible, use TLSv12 for best security. Recent Linux systems will include a version of OpenSSL that supports TLS version 1.2. Only if you are using MaxScale on a system that does not have OpenSSL with support for this should earlier versions be used. It is unlikely that TLS 1.1 will be available unless TLS 1.2 is also available. MAX will use the best available version. diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md index 8746eeda7..c8336a16e 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -11,6 +11,18 @@ of [2.0.0](./MaxScale-2.0.0-Release-Notes.md). For any problems you encounter, please consider submitting a bug report at [Jira](https://jira.mariadb.org). +## Changed default values + +### `strip_db_esc` + +The service parameter [_strip_db_esc_](../Getting-Started/Configuration-Guide.md#strip_db_esc) +now defaults to true. + +### `detect_stale_master` + +The [stale master detection](../Monitors/MySQL-Monitor.md#detect_stale_master) +feature is now enabled by default. + ## Updated Features ### Starting MariaDB MaxScale From a32b4bdf6b0067c9b9b06714aeb226feaa55950c Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 9 Sep 2016 16:01:16 +0300 Subject: [PATCH 28/36] Add missing examples to installation The CDC examples are now installed if the CDC protocol module is built. --- CMakeLists.txt | 1 - server/modules/protocol/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8df13a184..9119f5ad7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -306,4 +306,3 @@ if (NOT CMAKE_INSTALL_PREFIX EQUAL "/usr") install(DIRECTORY DESTINATION var/log/maxscale) install(DIRECTORY DESTINATION var/run/maxscale) endif() - diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index eb2dc0482..423ab83df 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -1,5 +1,6 @@ if(BUILD_CDC) add_subdirectory(CDC) + add_subdirectory(examples) endif() add_subdirectory(HTTPD) @@ -8,7 +9,6 @@ add_subdirectory(MySQLBackend) add_subdirectory(MySQLClient) add_subdirectory(telnetd) - if(BUILD_TESTS) add_subdirectory(testprotocol) endif() From 4a95439a8dfd3f55be45c06e5e5994a6c6c2037f Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 9 Sep 2016 16:38:27 +0300 Subject: [PATCH 29/36] Don't restart even on abnormal failures If systemd restarts MaxScale when an abnormal exit is detected, it is likely to happen again. This leads into a loop which causes multiple maxscale processes on the same machine. One example of this behavior is when systemd times MaxScale out when it is starting. --- etc/maxscale.service.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/maxscale.service.in b/etc/maxscale.service.in index 0ebeaeaab..982cbc00b 100644 --- a/etc/maxscale.service.in +++ b/etc/maxscale.service.in @@ -4,7 +4,7 @@ After=network.target [Service] Type=forking -Restart=on-abnormal +Restart=no PIDFile=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid ExecStartPre=/usr/bin/install -d @MAXSCALE_VARDIR@/run/maxscale -o maxscale -g maxscale ExecStart=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale From 7a144079b9a38e016bc7ca1fd6bc6ddfec935b52 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 8 Sep 2016 15:56:48 +0300 Subject: [PATCH 30/36] MXS-812: Fix active operation counters When a client executes commands which do not return results (for example inserting BLOB data via the C API), readwritesplit expects a result for each sent packet. This is a somewhat of a false assumption but it clears itself out when the session is closed normally. If the session is closed due to an error, the counter is not decremented. Each sesssion should only increase the number of active operation on a server by one operation. By checking that the session is not already executing an operation before incrementing the active operation count the runtime operation count will be correct. --- .../routing/readwritesplit/readwritesplit.c | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 0c980a631..f6a60f788 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -942,7 +942,7 @@ static void closeSession(ROUTER *instance, void *router_session) } #endif /** Clean operation counter in bref and in SERVER */ - while (BREF_IS_WAITING_RESULT(bref)) + if (BREF_IS_WAITING_RESULT(bref)) { bref_clear_state(bref, BREF_WAITING_RESULT); } @@ -955,6 +955,10 @@ static void closeSession(ROUTER *instance, void *router_session) /** decrease server current connection counters */ atomic_add(&bref->bref_backend->backend_conn_count, -1); } + else + { + ss_dassert(!BREF_IS_WAITING_RESULT(bref)); + } } /** Unlock */ rses_end_locked_router_action(router_cli_ses); @@ -2796,11 +2800,8 @@ static void bref_clear_state(backend_ref_t *bref, bref_state_t state) MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); return; } - if (state != BREF_WAITING_RESULT) - { - bref->bref_state &= ~state; - } - else + + if ((state & BREF_WAITING_RESULT) && (bref->bref_state & BREF_WAITING_RESULT)) { int prev1; int prev2; @@ -2825,6 +2826,8 @@ static void bref_clear_state(backend_ref_t *bref, bref_state_t state) } } } + + bref->bref_state &= ~state; } static void bref_set_state(backend_ref_t *bref, bref_state_t state) @@ -2834,11 +2837,8 @@ static void bref_set_state(backend_ref_t *bref, bref_state_t state) MXS_ERROR("[%s] Error: NULL parameter.", __FUNCTION__); return; } - if (state != BREF_WAITING_RESULT) - { - bref->bref_state |= state; - } - else + + if ((state & BREF_WAITING_RESULT) && (bref->bref_state & BREF_WAITING_RESULT) == 0) { int prev1; int prev2; @@ -2864,6 +2864,8 @@ static void bref_set_state(backend_ref_t *bref, bref_state_t state) bref->bref_backend->backend_server->port); } } + + bref->bref_state |= state; } /** From 76ea31bc2d742f1d95ac08fb8ae00c1b2effef8b Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 9 Sep 2016 17:02:41 +0300 Subject: [PATCH 31/36] Add MXS-812 to release notes MXS-812 is now mentioned in the 2.0.1 release notes. --- Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md index c8336a16e..fe6675efc 100644 --- a/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.0.1-Release-Notes.md @@ -115,6 +115,7 @@ Please consult [Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20in%20(2.0.0%2C%202.0.1)%20AND%20resolved%20%3E%3D%20-21d%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC) +* [MXS-812](https://jira.mariadb.org/browse/MXS-812): Number of conns not matching number of operations * [MXS-847](https://jira.mariadb.org/browse/MXS-847): server_down event is executed 8 times due to putting sever into maintenance mode * [MXS-845](https://jira.mariadb.org/browse/MXS-845): "Server down" event is re-triggered after maintenance mode is repeated * [MXS-842](https://jira.mariadb.org/browse/MXS-842): Unexpected / undocumented behaviour when multiple available masters from mmmon monitor From 717b623587278a31ac0d6a1937c469e009bde5bf Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 9 Sep 2016 20:39:16 +0300 Subject: [PATCH 32/36] Fix crash in server_free server_free tried to free a char array which wasn't malloc'ed. --- server/core/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/server/core/server.c b/server/core/server.c index 4de4e4d64..c4ef6426b 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -135,7 +135,6 @@ server_free(SERVER *tofreeserver) free(tofreeserver->protocol); free(tofreeserver->unique_name); free(tofreeserver->server_string); - free(tofreeserver->slaves); server_parameter_free(tofreeserver->parameters); if (tofreeserver->persistent) From e26f3795e29058b0dd466dce1fad884ee3377074 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sun, 11 Sep 2016 20:37:50 +0300 Subject: [PATCH 33/36] Remove useless parts from CHK_LOGFILE The CHK_LOGFILE macro first asserts that the values being checked are valid and then proceeds to evaluate it again. The result of this evaluation was not assigned to anything and it caused GCC 6.1.1 to produce a warning. --- utils/skygw_debug.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/utils/skygw_debug.h b/utils/skygw_debug.h index dd0ce10c4..15aa1b352 100644 --- a/utils/skygw_debug.h +++ b/utils/skygw_debug.h @@ -405,14 +405,6 @@ typedef enum skygw_chk_t lf->lf_name_suffix != NULL && \ lf->lf_full_file_name != NULL, \ "NULL in name variable\n"); \ - ss_debug( \ - (lf->lf_chk_top != CHK_NUM_LOGFILE || \ - lf->lf_chk_tail != CHK_NUM_LOGFILE ? \ - false : \ - (lf->lf_filepath == NULL || \ - lf->lf_name_prefix == NULL || \ - lf->lf_name_suffix == NULL || \ - lf->lf_full_file_name == NULL ? false : true));) \ } #define CHK_FILEWRITER(fwr) { \ From 2d229927fe3f479580a022a10c4a67221d3d276e Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 12 Sep 2016 06:46:56 +0300 Subject: [PATCH 34/36] Fix broken `if` in poll.c One if was not working due to an extra semicolon at the end of the line. --- server/core/poll.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/poll.c b/server/core/poll.c index cf97b3418..1f79aa5fb 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -225,7 +225,7 @@ poll_init() bitmask_init(&poll_mask); n_threads = config_threadcount(); thread_data = (THREAD_DATA *)MXS_MALLOC(n_threads * sizeof(THREAD_DATA)); - if (thread_data); + if (thread_data) { for (i = 0; i < n_threads; i++) { From 0d157b16f851bb874928b4ef6210205ab18a0eec Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 12 Sep 2016 06:50:07 +0300 Subject: [PATCH 35/36] Fix luafilter build failure The createInstance used the old signature. --- server/modules/filter/luafilter/luafilter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/modules/filter/luafilter/luafilter.c b/server/modules/filter/luafilter/luafilter.c index 9123e3ca9..80f2de37e 100644 --- a/server/modules/filter/luafilter/luafilter.c +++ b/server/modules/filter/luafilter/luafilter.c @@ -73,7 +73,7 @@ version() /* * The filter entry points */ -static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static FILTER *createInstance(const char *name, char **options, FILTER_PARAMETER **); static void *newSession(FILTER *instance, SESSION *session); static void closeSession(FILTER *instance, void *session); static void freeSession(FILTER *instance, void *session); @@ -169,7 +169,7 @@ ModuleInit() * @return The instance data for this new instance */ static FILTER * -createInstance(char **options, FILTER_PARAMETER **params) +createInstance(const char *name, char **options, FILTER_PARAMETER **params) { LUA_INSTANCE *my_instance; bool error = false; From 8be833640f7938dd4542e7ed9da2f74935c3e045 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 12 Sep 2016 09:35:11 +0300 Subject: [PATCH 36/36] Change restart type to on-abort MaxScale should only restart if an abort is detects, i.e. MaxScale crashes. --- etc/maxscale.service.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/maxscale.service.in b/etc/maxscale.service.in index 982cbc00b..84b76bcc3 100644 --- a/etc/maxscale.service.in +++ b/etc/maxscale.service.in @@ -4,7 +4,7 @@ After=network.target [Service] Type=forking -Restart=no +Restart=on-abort PIDFile=@MAXSCALE_VARDIR@/run/maxscale/maxscale.pid ExecStartPre=/usr/bin/install -d @MAXSCALE_VARDIR@/run/maxscale -o maxscale -g maxscale ExecStart=@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale --user=maxscale