diff --git a/.travis/build_maxscale.sh b/.travis/build_maxscale.sh index 916b8d37c..153ccb6e3 100644 --- a/.travis/build_maxscale.sh +++ b/.travis/build_maxscale.sh @@ -9,14 +9,17 @@ echo TRAVIS_BUILD_DIR: ${TRAVIS_BUILD_DIR} +# Configure the build environment +./BUILD/install_build_deps.sh + mkdir build cd build -cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=Y -DBUILD_AVRO=N +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=Y make -make test -sudo make install +make test || exit 1 +sudo make install sudo ./postinst maxscale --version diff --git a/BUILD/install_build_deps.sh b/BUILD/install_build_deps.sh index 4092ef4b9..804504a44 100755 --- a/BUILD/install_build_deps.sh +++ b/BUILD/install_build_deps.sh @@ -15,7 +15,7 @@ then sudo apt-get install -y --force-yes dpkg-dev git wget \ build-essential libssl-dev ncurses-dev bison flex \ perl libtool libpcre3-dev tcl tcl-dev uuid \ - uuid-dev libsqlite3-dev liblzma-dev libpam0g-dev + uuid-dev libsqlite3-dev liblzma-dev libpam0g-dev pkg-config ## separatelibgnutls installation process for Ubuntu Trusty cat /etc/*release | grep -E "Trusty|wheezy" if [ $? == 0 ] @@ -76,18 +76,18 @@ else fi # cmake -wget http://max-tst-01.mariadb.com/ci-repository/cmake-3.7.1-Linux-x86_64.tar.gz --no-check-certificate +wget -q http://max-tst-01.mariadb.com/ci-repository/cmake-3.7.1-Linux-x86_64.tar.gz --no-check-certificate if [ $? != 0 ] ; then echo "CMake can not be downloaded from Maxscale build server, trying from cmake.org" - wget https://cmake.org/files/v3.7/cmake-3.7.1-Linux-x86_64.tar.gz --no-check-certificate + wget -q https://cmake.org/files/v3.7/cmake-3.7.1-Linux-x86_64.tar.gz --no-check-certificate fi -sudo tar xzvf cmake-3.7.1-Linux-x86_64.tar.gz -C /usr/ --strip-components=1 +sudo tar xzf cmake-3.7.1-Linux-x86_64.tar.gz -C /usr/ --strip-components=1 cmake_version=`cmake --version | grep "cmake version" | awk '{ print $3 }'` if [ "$cmake_version" \< "3.7.1" ] ; then echo "cmake does not work! Trying to build from source" - wget https://cmake.org/files/v3.7/cmake-3.7.1.tar.gz --no-check-certificate - tar xzvf cmake-3.7.1.tar.gz + wget -q https://cmake.org/files/v3.7/cmake-3.7.1.tar.gz --no-check-certificate + tar xzf cmake-3.7.1.tar.gz cd cmake-3.7.1 ./bootstrap @@ -116,7 +116,7 @@ cd ../../ # TCL mkdir tcl cd tcl -wget --no-check-certificate http://prdownloads.sourceforge.net/tcl/tcl8.6.5-src.tar.gz +wget -q --no-check-certificate http://prdownloads.sourceforge.net/tcl/tcl8.6.5-src.tar.gz if [ $? != 0 ] then @@ -124,7 +124,7 @@ then exit 1 fi -tar xzvf tcl8.6.5-src.tar.gz +tar xzf tcl8.6.5-src.tar.gz cd tcl8.6.5/unix ./configure sudo make install @@ -149,7 +149,7 @@ sudo make install cd ../../ # Avro C API -wget -r -l1 -nH --cut-dirs=2 --no-parent -A.tar.gz --no-directories http://mirror.netinch.com/pub/apache/avro/stable/c +wget -q -r -l1 -nH --cut-dirs=2 --no-parent -A.tar.gz --no-directories http://mirror.netinch.com/pub/apache/avro/stable/c if [ $? != 0 ] then echo "Error getting avro-c" diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index b683827ce..8eb244c6d 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -28,6 +28,7 @@ the master. There is also limited capability for rejoining nodes. For more details, please refer to: +* [MariaDB MaxScale 2.2.3 Release Notes](Release-Notes/MaxScale-2.2.3-Release-Notes.md) * [MariaDB MaxScale 2.2.2 Release Notes](Release-Notes/MaxScale-2.2.2-Release-Notes.md) * [MariaDB MaxScale 2.2.1 Release Notes](Release-Notes/MaxScale-2.2.1-Release-Notes.md) * [MariaDB MaxScale 2.2.0 Release Notes](Release-Notes/MaxScale-2.2.0-Release-Notes.md) diff --git a/Documentation/Release-Notes/MaxScale-2.2.3-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.2.3-Release-Notes.md index cf1f85e50..90db0ff3c 100644 --- a/Documentation/Release-Notes/MaxScale-2.2.3-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.2.3-Release-Notes.md @@ -17,6 +17,18 @@ being monitored by the mariadbmon monitor, the current GTID position will be displayed in the newly added column. If no GTID is available, an empty value is returned. +### MaxAdmin input from scripts + +The failure to set terminal attributes for MaxScale is no longer considered an +error as scripts most often do not have an actual terminal that control the +process. This means that passwords and other commands can be passed to MaxAdmin +without a controlling terminal. + +### MaxCtrl password input + +MaxCtrl can now query the password from the user. This allows passwords to be +given without giving them as process arguments. + ## Dropped Features ## New Features @@ -25,6 +37,13 @@ returned. [Here is a list of bugs fixed in MaxScale 2.2.3.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.2.3) +* [MXS-1698](https://jira.mariadb.org/browse/MXS-1698) error:140940F5:SSL routines:ssl3_read_bytes:unexpected record +* [MXS-1697](https://jira.mariadb.org/browse/MXS-1697) MaxScale 2.2.2 missing avrorouter library +* [MXS-1693](https://jira.mariadb.org/browse/MXS-1693) In Maxscale 2.2.2 getting users with native password from mysql backends does not work +* [MXS-1688](https://jira.mariadb.org/browse/MXS-1688) Some date functions are not parsed properly with schemarouter +* [MXS-1684](https://jira.mariadb.org/browse/MXS-1684) Empty space on a line in rule file confuses dbfwfilter which refuses to start +* [MXS-1683](https://jira.mariadb.org/browse/MXS-1683) Commands that take passwords should allow input from stdin and not just from controlling terminals + ## Known Issues and Limitations There are some limitations and known issues within this version of MaxScale. diff --git a/client/maxadmin.c b/client/maxadmin.c index 9ef3a33ce..3866aa164 100644 --- a/client/maxadmin.c +++ b/client/maxadmin.c @@ -43,6 +43,9 @@ #ifdef HISTORY #include +#define USE_HIST 1 +#else +#define USE_HIST 0 #endif #define MAX_PASSWORD_LEN 80 @@ -62,6 +65,7 @@ static void read_inifile(char **socket, char **hostname, char **port, char **user, char **passwd, int *editor); static bool getPassword(char *password, size_t length); +static void rtrim(char *str); #ifdef HISTORY @@ -93,6 +97,128 @@ static struct option long_options[] = #define MAXADMIN_DEFAULT_USER "admin" #define MAXADMIN_BUFFER_SIZE 2048 +static bool term_error = false; + +bool process_command(int so, char* buf) +{ + bool rval = true; + + if (isquit(buf)) + { + rval = false; + } + else if (!strncasecmp(buf, "source", 6)) + { + char *ptr; + + /* Find the filename */ + ptr = &buf[strlen("source")]; + while (*ptr && isspace(*ptr)) + { + ptr++; + } + + DoSource(so, ptr); + } + else if (*buf) + { + if (!sendCommand(so, buf)) + { + rval = false; + } + } + + return rval; +} + +void cmd_with_history(int so, char** argv, bool use_emacs) +{ +#ifdef HISTORY + char *buf; + EditLine *el = NULL; + Tokenizer *tok; + History *hist; + HistEvent ev; + + hist = history_init(); /* Init the builtin history */ + + /* Remember 100 events */ + history(hist, &ev, H_SETSIZE, 100); + + tok = tok_init(NULL); /* Initialize the tokenizer */ + + /* Initialize editline */ + el = el_init(*argv, stdin, stdout, stderr); + + if (use_emacs) + { + el_set(el, EL_EDITOR, "emacs"); /** Editor is emacs */ + } + else + { + el_set(el, EL_EDITOR, "vi"); /* Default editor is vi */ + } + el_set(el, EL_SIGNAL, 1); /* Handle signals gracefully */ + el_set(el, EL_PROMPT, prompt); /* Set the prompt function */ + + /* Tell editline to use this history interface */ + el_set(el, EL_HIST, history, hist); + + /* + * Bind j, k in vi command mode to previous and next line, instead + * of previous and next history. + */ + el_set(el, EL_BIND, "-a", "k", "ed-prev-line", NULL); + el_set(el, EL_BIND, "-a", "j", "ed-next-line", NULL); + + /* + * Source the user's defaults file. + */ + el_source(el, NULL); + + int num = 0; + + while ((buf = (char *) el_gets(el, &num))) + { + rtrim(buf); + history(hist, &ev, H_ENTER, buf); + if (!strcasecmp(buf, "history")) + { + for (int rv = history(hist, &ev, H_LAST); rv != -1; + rv = history(hist, &ev, H_PREV)) + { + fprintf(stdout, "%4d %s\n", ev.num, ev.str); + } + } + else if (!process_command(so, buf)) + { + break; + } + } + + el_end(el); + tok_end(tok); + history_end(hist); +#endif +} + +void cmd_no_history(int so) +{ + char buf[MAXADMIN_BUFFER_SIZE]; + while (printf("MaxScale> ") && fgets(buf, 1024, stdin) != NULL) + { + rtrim(buf); + if (!strcasecmp(buf, "history")) + { + fprintf(stderr, "History not supported in this version.\n"); + } + else if (!process_command(so, buf)) + { + break; + } + } +} + /** * The main for the maxadmin client * @@ -102,15 +228,6 @@ static struct option long_options[] = int main(int argc, char **argv) { -#ifdef HISTORY - char *buf; - EditLine *el = NULL; - Tokenizer *tok; - History *hist; - HistEvent ev; -#else - char buf[MAXADMIN_BUFFER_SIZE]; -#endif char *hostname = NULL; char *port = NULL; char *user = NULL; @@ -293,105 +410,16 @@ main(int argc, char **argv) } (void) setlocale(LC_CTYPE, ""); -#ifdef HISTORY - hist = history_init(); /* Init the builtin history */ - /* Remember 100 events */ - history(hist, &ev, H_SETSIZE, 100); - tok = tok_init(NULL); /* Initialize the tokenizer */ - - /* Initialize editline */ - el = el_init(*argv, stdin, stdout, stderr); - - if (use_emacs) + if (!term_error && USE_HIST) { - el_set(el, EL_EDITOR, "emacs"); /** Editor is emacs */ + cmd_with_history(so, argv, use_emacs); } else { - el_set(el, EL_EDITOR, "vi"); /* Default editor is vi */ - } - el_set(el, EL_SIGNAL, 1); /* Handle signals gracefully */ - el_set(el, EL_PROMPT, prompt); /* Set the prompt function */ - - /* Tell editline to use this history interface */ - el_set(el, EL_HIST, history, hist); - - /* - * Bind j, k in vi command mode to previous and next line, instead - * of previous and next history. - */ - el_set(el, EL_BIND, "-a", "k", "ed-prev-line", NULL); - el_set(el, EL_BIND, "-a", "j", "ed-next-line", NULL); - - /* - * Source the user's defaults file. - */ - el_source(el, NULL); - - int num; - while ((buf = (char *) el_gets(el, &num)) != NULL && num != 0) - { -#else - while (printf("MaxScale> ") && fgets(buf, 1024, stdin) != NULL) - { - int num = strlen(buf); -#endif - /* Strip trailing \n\r */ - for (int i = num - 1; buf[i] == '\r' || buf[i] == '\n'; i--) - { - buf[i] = 0; - } - -#ifdef HISTORY - history(hist, &ev, H_ENTER, buf); -#endif - - if (isquit(buf)) - { - break; - } - else if (!strcasecmp(buf, "history")) - { -#ifdef HISTORY - int rv; - for (rv = history(hist, &ev, H_LAST); rv != -1; - rv = history(hist, &ev, H_PREV)) - { - fprintf(stdout, "%4d %s\n", - ev.num, ev.str); - } -#else - fprintf(stderr, "History not supported in this version.\n"); -#endif - } - else if (!strncasecmp(buf, "source", 6)) - { - char *ptr; - - /* Find the filename */ - ptr = &buf[strlen("source")]; - while (*ptr && isspace(*ptr)) - { - ptr++; - } - - DoSource(so, ptr); - } - else if (*buf) - { - if (!sendCommand(so, buf)) - { - return 0; - } - } + cmd_no_history(so); } -#ifdef HISTORY - el_end(el); - tok_end(tok); - history_end(hist); -#endif close(so); return 0; } @@ -937,8 +965,7 @@ read_inifile(char **socket, */ bool getPassword(char *passwd, size_t len) { - bool gotten = false; - + bool err = false; struct termios tty_attr; tcflag_t c_lflag; @@ -948,36 +975,55 @@ bool getPassword(char *passwd, size_t len) tty_attr.c_lflag &= ~ICANON; tty_attr.c_lflag &= ~ECHO; - if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0) + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) != 0) { - printf("Password: "); - if (fgets(passwd, len, stdin) == NULL) - { - printf("Failed to read password\n"); - } + err = true; + } + } + else + { + err = true; + } - tty_attr.c_lflag = c_lflag; + if (err) + { + fprintf(stderr, + "Warning: Could not configure terminal. Terminal echo is still enabled. This\n" + "means that the password will be visible on the controlling terminal when\n" + "it is written!\n"); + } - if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0) - { - int i = strlen(passwd); + printf("Password: "); + if (fgets(passwd, len, stdin) == NULL) + { + printf("Failed to read password\n"); + } - if (i > 1) - { - passwd[i - 1] = '\0'; - } + if (!err) + { + tty_attr.c_lflag = c_lflag; - printf("\n"); - - gotten = true; - } + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) != 0) + { + err = true; } } - if (!gotten) + int i = strlen(passwd); + + if (i > 0) { - fprintf(stderr, "Could not configure terminal.\n"); + passwd[i - 1] = '\0'; } - return gotten; + printf("\n"); + + + // Store failure globally so that interactive parts are skipped + if (err) + { + term_error = true; + } + + return *passwd; } diff --git a/maxctrl/lib/common.js b/maxctrl/lib/common.js index 5f2ad151c..cab6d9057 100644 --- a/maxctrl/lib/common.js +++ b/maxctrl/lib/common.js @@ -17,6 +17,7 @@ var Table = require('cli-table'); var consoleLib = require('console') var os = require('os') var fs = require('fs') +var readlineSync = require('readline-sync') module.exports = function() { @@ -27,6 +28,13 @@ module.exports = function() { // servers. this.maxctrl = function(argv, cb) { + // No password given, ask it from the command line + if (argv.p == '') { + argv.p = readlineSync.question('Enter password: ', { + hideEchoBack: true + }) + } + // Split the hostnames, separated by commas argv.hosts = argv.hosts.split(',') @@ -209,7 +217,7 @@ module.exports = function() { base = 'https://' } - return base + argv.user + ':' + argv.password + '@' + host + '/v1/' + endpoint + return base + argv.u + ':' + argv.p + '@' + host + '/v1/' + endpoint } this.OK = function() { diff --git a/maxctrl/lib/core.js b/maxctrl/lib/core.js index a386c9642..c3bd62a1a 100644 --- a/maxctrl/lib/core.js +++ b/maxctrl/lib/core.js @@ -31,7 +31,7 @@ program }) .option('p', { alias: 'password', - describe: 'Password for the user', + describe: 'Password for the user. To input the password manually, give -p as the last argument or use --password=\'\'', default: 'mariadb', type: 'string' }) @@ -104,7 +104,7 @@ program .demandCommand(1, 'At least one command is required') .command('*', 'the default command', {}, function(argv) { maxctrl(argv, function() { - return error('Unknown command. See output of `help` for a list of commands.') + return error('Unknown command ' + JSON.stringify(argv._) + '. See output of `help` for a list of commands.') }) }) diff --git a/maxctrl/package.json b/maxctrl/package.json index 3ae3c241e..7e42d4a67 100644 --- a/maxctrl/package.json +++ b/maxctrl/package.json @@ -19,6 +19,7 @@ "cli-table": "^0.3.1", "lodash": "^4.17.4", "lodash-getpath": "^0.2.4", + "readline-sync": "^1.4.9", "request": "^2.81.0", "request-promise-native": "^1.0.3", "yargs": "^8.0.2" diff --git a/maxscale-system-test/mxs1476.cpp b/maxscale-system-test/mxs1476.cpp index 67852aecd..56c3719ba 100644 --- a/maxscale-system-test/mxs1476.cpp +++ b/maxscale-system-test/mxs1476.cpp @@ -6,6 +6,14 @@ #include "testconnections.h" +void list_servers(TestConnections& test) +{ + int rc; + char *output = test.maxscales->ssh_node_output_f(0, true, &rc, "maxadmin list servers"); + test.tprintf("%s", output); + free(output); +} + void do_test(TestConnections& test, int master, int slave) { test.maxscales->connect_maxscale(0); @@ -15,24 +23,32 @@ void do_test(TestConnections& test, int master, int slave) test.tprintf("Stop a slave node and perform an insert"); test.galera->block_node(slave); - sleep(5); + sleep(10); + list_servers(test); + test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)"); test.tprintf("Start the slave node and perform another insert"); test.galera->unblock_node(slave); - sleep(5); + sleep(10); + list_servers(test); + test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)"); test.maxscales->close_maxscale_connections(0); test.tprintf("Stop the master node and perform an insert"); test.galera->block_node(master); - sleep(5); + sleep(10); + list_servers(test); + test.maxscales->connect_maxscale(0); test.try_query(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)"); test.tprintf("Start the master node and perform another insert (expecting failure)"); test.galera->unblock_node(master); - sleep(5); + sleep(10); + list_servers(test); + test.add_result(execute_query_silent(test.maxscales->conn_rwsplit[0], "INSERT INTO test.t1 VALUES (1)") == 0, "Query should fail"); test.maxscales->close_maxscale_connections(0); diff --git a/server/core/messagequeue.cc b/server/core/messagequeue.cc index a741edcf3..2731394a0 100644 --- a/server/core/messagequeue.cc +++ b/server/core/messagequeue.cc @@ -176,6 +176,11 @@ bool MessageQueue::post(const Message& message) const { ssize_t n = write(m_write_fd, &message, sizeof(message)); rv = (n == sizeof(message)); + + if (n == -1) + { + MXS_ERROR("Failed to write message: %d, %s", errno, mxs_strerror(errno)); + } } else { diff --git a/server/core/service.cc b/server/core/service.cc index 4a6f453b1..531736845 100644 --- a/server/core/service.cc +++ b/server/core/service.cc @@ -624,11 +624,21 @@ int service_launch_all() SERVICE *ptr; int n = 0, i; bool error = false; + int num_svc = 0; + for (ptr = allServices; ptr; ptr = ptr->next) + { + num_svc++; + } + + MXS_NOTICE("Starting a total of %d services...", num_svc); + + int curr_svc = 1; ptr = allServices; while (ptr && !ptr->svc_do_shutdown) { n += (i = serviceInitialize(ptr)); + MXS_NOTICE("Service '%s' started (%d/%d)", ptr->name, curr_svc++, num_svc); if (i == 0) { diff --git a/server/modules/protocol/MySQL/mysql_common.cc b/server/modules/protocol/MySQL/mysql_common.cc index ddc6d0735..e97c8324d 100644 --- a/server/modules/protocol/MySQL/mysql_common.cc +++ b/server/modules/protocol/MySQL/mysql_common.cc @@ -1376,7 +1376,7 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) with_ssl, ssl_established); ss_dassert(buffer); - if (with_ssl) + if (with_ssl && !ssl_established) { if (dcb_write(dcb, buffer) && dcb_connect_SSL(dcb) >= 0) { diff --git a/server/modules/routing/readwritesplit/readwritesplit.cc b/server/modules/routing/readwritesplit/readwritesplit.cc index 75d530e27..f3420785c 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.cc +++ b/server/modules/routing/readwritesplit/readwritesplit.cc @@ -1253,10 +1253,12 @@ static void clientReply(MXS_ROUTER *instance, rses->expected_responses--; ss_dassert(rses->expected_responses >= 0); ss_dassert(backend->get_reply_state() == REPLY_STATE_DONE); + MXS_INFO("Reply complete, last reply from %s", backend->name()); } else { - MXS_DEBUG("Reply not yet complete, waiting for %d replies", rses->expected_responses); + MXS_INFO("Reply not yet complete. Waiting for %d replies, got one from %s", + rses->expected_responses, backend->name()); } /**