|  | /*****************************************************************************\ | 
|  | *  mysql_common.c - common functions for the mysql storage plugin. | 
|  | ***************************************************************************** | 
|  | *  Copyright (C) 2004-2007 The Regents of the University of California. | 
|  | *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). | 
|  | *  Written by Danny Auble <da@llnl.gov> | 
|  | * | 
|  | *  This file is part of Slurm, a resource management program. | 
|  | *  For details, see <https://slurm.schedmd.com/>. | 
|  | *  Please also read the included file: DISCLAIMER. | 
|  | * | 
|  | *  Slurm is free software; you can redistribute it and/or modify it under | 
|  | *  the terms of the GNU General Public License as published by the Free | 
|  | *  Software Foundation; either version 2 of the License, or (at your option) | 
|  | *  any later version. | 
|  | * | 
|  | *  In addition, as a special exception, the copyright holders give permission | 
|  | *  to link the code of portions of this program with the OpenSSL library under | 
|  | *  certain conditions as described in each individual source file, and | 
|  | *  distribute linked combinations including the two. You must obey the GNU | 
|  | *  General Public License in all respects for all of the code used other than | 
|  | *  OpenSSL. If you modify file(s) with this exception, you may extend this | 
|  | *  exception to your version of the file(s), but you are not obligated to do | 
|  | *  so. If you do not wish to do so, delete this exception statement from your | 
|  | *  version.  If you delete this exception statement from all source files in | 
|  | *  the program, then also delete it here. | 
|  | * | 
|  | *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY | 
|  | *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|  | *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more | 
|  | *  details. | 
|  | * | 
|  | *  You should have received a copy of the GNU General Public License along | 
|  | *  with Slurm; if not, write to the Free Software Foundation, Inc., | 
|  | *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA. | 
|  | * | 
|  | *  This file is patterned after jobcomp_linux.c, written by Morris Jette and | 
|  | *  Copyright (C) 2002 The Regents of the University of California. | 
|  | \*****************************************************************************/ | 
|  |  | 
|  | #include "config.h" | 
|  |  | 
|  | #include "mysql_common.h" | 
|  | #include "src/common/log.h" | 
|  | #include "src/common/xstring.h" | 
|  | #include "src/common/xmalloc.h" | 
|  | #include "src/common/timers.h" | 
|  | #include "src/common/slurm_protocol_api.h" | 
|  | #include "src/common/read_config.h" | 
|  | #include "src/slurmdbd/read_config.h" | 
|  |  | 
|  | #define MAX_DEADLOCK_ATTEMPTS 10 | 
|  |  | 
|  | static char *table_defs_table = "table_defs_table"; | 
|  |  | 
|  | typedef struct { | 
|  | char *name; | 
|  | char *columns; | 
|  | bool non_unique; | 
|  | } db_key_t; | 
|  |  | 
|  | static void _destroy_db_key(void *arg) | 
|  | { | 
|  | db_key_t *db_key = (db_key_t *)arg; | 
|  |  | 
|  | if (db_key) { | 
|  | xfree(db_key->name); | 
|  | xfree(db_key->columns); | 
|  | xfree(db_key); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int _find_db_key(void *x, void *key) | 
|  | { | 
|  | db_key_t * db_key = (db_key_t *) x; | 
|  |  | 
|  | if (xstrcmp(db_key->name, (char *) key)) | 
|  | return 0; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is set on function entry */ | 
|  | static int _clear_results(MYSQL *db_conn) | 
|  | { | 
|  | MYSQL_RES *result = NULL; | 
|  | int rc = 0; | 
|  |  | 
|  | do { | 
|  | /* did current statement return data? */ | 
|  | if ((result = mysql_store_result(db_conn))) | 
|  | mysql_free_result(result); | 
|  |  | 
|  | /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */ | 
|  | if ((rc = mysql_next_result(db_conn)) > 0) | 
|  | error("Could not execute statement %d %s", | 
|  | mysql_errno(db_conn), | 
|  | mysql_error(db_conn)); | 
|  | } while (rc == 0); | 
|  |  | 
|  | if (rc > 0) { | 
|  | errno = rc; | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is set on function entry */ | 
|  | static MYSQL_RES *_get_first_result(MYSQL *db_conn) | 
|  | { | 
|  | MYSQL_RES *result = NULL; | 
|  | int rc = 0; | 
|  | do { | 
|  | /* did current statement return data? */ | 
|  | if ((result = mysql_store_result(db_conn))) | 
|  | return result; | 
|  |  | 
|  | /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */ | 
|  | if ((rc = mysql_next_result(db_conn)) > 0) | 
|  | debug3("error: Could not execute statement %d", rc); | 
|  |  | 
|  | } while (rc == 0); | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is set on function entry */ | 
|  | static MYSQL_RES *_get_last_result(MYSQL *db_conn) | 
|  | { | 
|  | MYSQL_RES *result = NULL; | 
|  | MYSQL_RES *last_result = NULL; | 
|  | int rc = 0; | 
|  | do { | 
|  | /* did current statement return data? */ | 
|  | if ((result = mysql_store_result(db_conn))) { | 
|  | if (last_result) | 
|  | mysql_free_result(last_result); | 
|  | last_result = result; | 
|  | } | 
|  | /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */ | 
|  | if ((rc = mysql_next_result(db_conn)) > 0) | 
|  | debug3("error: Could not execute statement %d", rc); | 
|  | } while (rc == 0); | 
|  |  | 
|  | return last_result; | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is set on function entry */ | 
|  | static int _mysql_query_internal(MYSQL *db_conn, char *query) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  | int deadlock_attempt = 0; | 
|  |  | 
|  | try_again: | 
|  | if (!db_conn) | 
|  | fatal("You haven't inited this storage yet."); | 
|  |  | 
|  | /* clear out the old results so we don't get a 2014 error */ | 
|  | _clear_results(db_conn); | 
|  | if (mysql_query(db_conn, query)) { | 
|  | const char *err_str = mysql_error(db_conn); | 
|  | errno = mysql_errno(db_conn); | 
|  | if (errno == ER_NO_SUCH_TABLE) { | 
|  | debug4("This could happen often and is expected.\n" | 
|  | "mysql_query failed: %d %s\n%s", | 
|  | errno, err_str, query); | 
|  | errno = 0; | 
|  | goto end_it; | 
|  | } | 
|  | if (errno == ER_LOCK_DEADLOCK) { | 
|  | /* | 
|  | * Mysql detected a deadlock and we should retry | 
|  | * a few times since this is mainly a race condition | 
|  | */ | 
|  | deadlock_attempt++; | 
|  |  | 
|  | if (deadlock_attempt < MAX_DEADLOCK_ATTEMPTS) { | 
|  | error("%s: deadlock detected attempt %u/%u: %d %s", | 
|  | __func__, deadlock_attempt, | 
|  | MAX_DEADLOCK_ATTEMPTS, errno, err_str); | 
|  | goto try_again; | 
|  | } else { | 
|  | fatal("%s: unable to resolve deadlock with attempts %u/%u: %d %s\nPlease call 'show engine innodb status;' in MySQL/MariaDB and open a bug report with SchedMD.", | 
|  | __func__, deadlock_attempt, | 
|  | MAX_DEADLOCK_ATTEMPTS, errno, err_str); | 
|  | } | 
|  | } else if (errno == ER_LOCK_WAIT_TIMEOUT) { | 
|  | /* FIXME: If we get ER_LOCK_WAIT_TIMEOUT here we need | 
|  | * to restart the connections, but it appears restarting | 
|  | * the calling program is the only way to handle this. | 
|  | * If anyone in the future figures out a way to handle | 
|  | * this, super.  Until then we will need to restart the | 
|  | * calling program if you ever get this error. | 
|  | */ | 
|  | fatal("mysql gave ER_LOCK_WAIT_TIMEOUT as an error. " | 
|  | "The only way to fix this is restart the " | 
|  | "calling program"); | 
|  | } else if (errno == ER_HOST_IS_BLOCKED) { | 
|  | fatal("MySQL gave ER_HOST_IS_BLOCKED as an error. " | 
|  | "You will need to call 'mysqladmin flush-hosts' " | 
|  | "to regain connectivity."); | 
|  | } | 
|  | error("mysql_query failed: %d %s\n%s", errno, err_str, query); | 
|  | rc = SLURM_ERROR; | 
|  | } | 
|  | end_it: | 
|  | /* | 
|  | * Starting in MariaDB 10.2 many of the api commands started | 
|  | * setting errno erroneously. | 
|  | */ | 
|  | if (!rc) | 
|  | errno = 0; | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Determine if a database server upgrade has taken place and if so, check to | 
|  | * see if the candidate table alteration query should be used to alter the table | 
|  | * to its expected settings. Returns true if so. | 
|  | * | 
|  | * Background: | 
|  | * | 
|  | * From the MariaDB docs: | 
|  | *  Before MariaDB 10.2.1, BLOB and TEXT columns could not be assigned a DEFAULT | 
|  | *  value. This restriction was lifted in MariaDB 10.2.1. | 
|  | * | 
|  | * If a site begins using MariaDB >= 10.2.1 and is either using an existing | 
|  | * Slurm database from an earlier version or has restored one from a dump from | 
|  | * an earlier version or from any version of MySQL, some text/blob default | 
|  | * values will need to be altered to avoid failures from subsequent queries from | 
|  | * slurmdbd that set affected fields to DEFAULT (see bug#13606). | 
|  | * | 
|  | * Note that only one column from one table ('preempt' from qos_table) is | 
|  | * checked to determine if an upgrade has taken place with the assumption that | 
|  | * if its default value is not correct then the same is true for similar | 
|  | * text/blob columns from other tables and they will also need to be altered. | 
|  | * | 
|  | * The qos_table has been chosen for this check because it is the last table | 
|  | * with condition to be created. If that condition changes this should be | 
|  | * re-evaluated. | 
|  | */ | 
|  | static bool _alter_table_after_upgrade(mysql_conn_t *mysql_conn, | 
|  | char *table_alter_query) | 
|  | { | 
|  | static bool have_value = false, upgraded = false; | 
|  | MYSQL_RES *result = NULL; | 
|  | MYSQL_ROW row; | 
|  |  | 
|  | /* check to see if upgrade has happened */ | 
|  | if (!have_value) { | 
|  | const char *tmp_char; | 
|  | char *query; | 
|  | /* | 
|  | * confirm MariaDB is being used to avoid any ambiguity with | 
|  | * MySQL versions | 
|  | */ | 
|  | tmp_char = mysql_get_server_info(mysql_conn->db_conn); | 
|  | if (xstrcasestr(tmp_char, "mariadb") && | 
|  | (mysql_get_server_version(mysql_conn->db_conn) >= 100201)) { | 
|  | query = "show columns from `qos_table` like 'preempt'"; | 
|  | result = mysql_db_query_ret(mysql_conn, query, 0); | 
|  | if (result) { | 
|  | /* | 
|  | * row[4] holds the column's default value and | 
|  | * if it's not empty ('') then it will need to | 
|  | * be altered and an upgrade is assumed | 
|  | */ | 
|  | if ((row = mysql_fetch_row(result)) && | 
|  | !xstrcasecmp(row[1], "text") && | 
|  | xstrcmp(row[4], "''")) | 
|  | upgraded = true; | 
|  | mysql_free_result(result); | 
|  | } | 
|  | } | 
|  | have_value = true; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If upgrade detected and the table alter query string contains an | 
|  | * empty string default then the query should be executed. The latter | 
|  | * check avoids unnecessary table alterations. | 
|  | */ | 
|  | if (upgraded && xstrcasestr(table_alter_query, "default ''")) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is NOT set on function entry */ | 
|  | static int _mysql_make_table_current(mysql_conn_t *mysql_conn, char *table_name, | 
|  | storage_field_t *fields, char *ending) | 
|  | { | 
|  | char *query = NULL; | 
|  | char *correct_query = NULL; | 
|  | MYSQL_RES *result = NULL; | 
|  | MYSQL_ROW row; | 
|  | int i = 0; | 
|  | list_t *columns = NULL; | 
|  | list_itr_t *itr = NULL; | 
|  | char *col = NULL; | 
|  | int adding = 0; | 
|  | int run_update = 0; | 
|  | char *primary_key = NULL; | 
|  | char *unique_index = NULL; | 
|  | int old_primary = 0; | 
|  | char *temp = NULL, *temp2 = NULL; | 
|  | list_t *keys_list = NULL; | 
|  | db_key_t *db_key = NULL; | 
|  |  | 
|  | DEF_TIMERS; | 
|  |  | 
|  | /* figure out the unique keys in the table */ | 
|  | query = xstrdup_printf("show index from %s where non_unique=0", | 
|  | table_name); | 
|  | if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) { | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query); | 
|  |  | 
|  | keys_list = list_create(_destroy_db_key); | 
|  | while ((row = mysql_fetch_row(result))) { | 
|  | /* | 
|  | * row[2] = key name | 
|  | * row[4] = column name | 
|  | */ | 
|  | if (!xstrcasecmp(row[2], "PRIMARY")) { | 
|  | old_primary = 1; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | db_key = list_find_first(keys_list, _find_db_key, row[2]); | 
|  |  | 
|  | if (db_key) { | 
|  | xstrfmtcat(db_key->columns, ", %s", row[4]); | 
|  | } else { | 
|  | db_key = xmalloc(sizeof(db_key_t)); | 
|  | db_key->name = xstrdup(row[2]); | 
|  | db_key->columns = xstrdup(row[4]); | 
|  | db_key->non_unique = false; | 
|  | list_append(keys_list, db_key); | 
|  | } | 
|  | } | 
|  | mysql_free_result(result); | 
|  |  | 
|  | /* figure out the non-unique keys in the table */ | 
|  | query = xstrdup_printf("show index from %s where non_unique=1", | 
|  | table_name); | 
|  | if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) { | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query); | 
|  |  | 
|  | while ((row = mysql_fetch_row(result))) { | 
|  | db_key = list_find_first(keys_list, _find_db_key, row[2]); | 
|  |  | 
|  | if (db_key) { | 
|  | xstrfmtcat(db_key->columns, ", %s", row[4]); | 
|  | } else { | 
|  | db_key = xmalloc(sizeof(db_key_t)); | 
|  | db_key->name = xstrdup(row[2]); // name | 
|  | db_key->columns = xstrdup(row[4]); // column name | 
|  | db_key->non_unique = true; | 
|  | list_append(keys_list, db_key); // don't use list_push | 
|  | } | 
|  | } | 
|  | mysql_free_result(result); | 
|  |  | 
|  | /* figure out the existing columns in the table */ | 
|  | query = xstrdup_printf("show columns from %s", table_name); | 
|  | if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) { | 
|  | xfree(query); | 
|  | FREE_NULL_LIST(keys_list); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query); | 
|  | columns = list_create(xfree_ptr); | 
|  | while ((row = mysql_fetch_row(result))) { | 
|  | col = xstrdup(row[0]); //Field | 
|  | list_append(columns, col); | 
|  | } | 
|  | mysql_free_result(result); | 
|  |  | 
|  |  | 
|  | itr = list_iterator_create(columns); | 
|  | /* In MySQL 5.7.4 we lost the ability to run 'alter ignore'.  This was | 
|  | * needed when converting old tables to new schemas.  If people convert | 
|  | * in the future from an older version of Slurm that needed the ignore | 
|  | * to work they will have to downgrade mysql to <= 5.7.3 to make things | 
|  | * work correctly or manually edit the database to get things to work. | 
|  | */ | 
|  | /* | 
|  | * `query` is compared against the current table_defs_table.definition | 
|  | * and run if they are different. `correct_query` is inserted into the | 
|  | * table, so it must be what future `query` schemas will be. | 
|  | * In other words, `query` transitions the table to the new schema, | 
|  | * `correct_query` represents the new schema | 
|  | */ | 
|  | query = xstrdup_printf("alter table %s", table_name); | 
|  | correct_query = xstrdup(query); | 
|  | START_TIMER; | 
|  | while (fields[i].name) { | 
|  | int found = 0; | 
|  |  | 
|  | list_iterator_reset(itr); | 
|  | while ((col = list_next(itr))) { | 
|  | if (!xstrcmp(col, fields[i].name)) { | 
|  | xstrfmtcat(query, " modify `%s` %s,", | 
|  | fields[i].name, | 
|  | fields[i].options); | 
|  | xstrfmtcat(correct_query, " modify `%s` %s,", | 
|  | fields[i].name, | 
|  | fields[i].options); | 
|  | list_delete_item(itr); | 
|  | found = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!found) { | 
|  | if (i) { | 
|  | info("adding column %s after %s in table %s", | 
|  | fields[i].name, | 
|  | fields[i-1].name, | 
|  | table_name); | 
|  | xstrfmtcat(query, " add `%s` %s after %s,", | 
|  | fields[i].name, | 
|  | fields[i].options, | 
|  | fields[i-1].name); | 
|  | xstrfmtcat(correct_query, " modify `%s` %s,", | 
|  | fields[i].name, | 
|  | fields[i].options); | 
|  | } else { | 
|  | info("adding column %s at the beginning " | 
|  | "of table %s", | 
|  | fields[i].name, | 
|  | table_name); | 
|  | xstrfmtcat(query, " add `%s` %s first,", | 
|  | fields[i].name, | 
|  | fields[i].options); | 
|  | xstrfmtcat(correct_query, " modify `%s` %s,", | 
|  | fields[i].name, | 
|  | fields[i].options); | 
|  | } | 
|  | adding = 1; | 
|  | } | 
|  |  | 
|  | i++; | 
|  | } | 
|  |  | 
|  | list_iterator_reset(itr); | 
|  | while ((col = list_next(itr))) { | 
|  | adding = 1; | 
|  | info("dropping column %s from table %s", col, table_name); | 
|  | xstrfmtcat(query, " drop %s,", col); | 
|  | } | 
|  |  | 
|  | list_iterator_destroy(itr); | 
|  | FREE_NULL_LIST(columns); | 
|  |  | 
|  | if ((temp = strstr(ending, "primary key ("))) { | 
|  | int open = 0, close =0; | 
|  | int end = 0; | 
|  | while (temp[end++]) { | 
|  | if (temp[end] == '(') | 
|  | open++; | 
|  | else if (temp[end] == ')') | 
|  | close++; | 
|  | else | 
|  | continue; | 
|  | if (open == close) | 
|  | break; | 
|  | } | 
|  | if (temp[end]) { | 
|  | end++; | 
|  | primary_key = xstrndup(temp, end); | 
|  | if (old_primary) | 
|  | xstrcat(query, " drop primary key,"); | 
|  | xstrcat(correct_query, " drop primary key,"); | 
|  | xstrfmtcat(query, " add %s,",  primary_key); | 
|  | xstrfmtcat(correct_query, " add %s,",  primary_key); | 
|  |  | 
|  | xfree(primary_key); | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((temp = strstr(ending, "unique index"))) { | 
|  | int open = 0, close = 0; | 
|  | /* sizeof includes NULL, and end should start 1 back */ | 
|  | int end = sizeof("unique index") - 2; | 
|  | char *udex_name = NULL, *name_marker = NULL; | 
|  | while (temp[end++]) { | 
|  | /* | 
|  | * Extracts the index name, which is given explicitly | 
|  | * or is the name of the first field included in the | 
|  | * index. | 
|  | * "unique index indexname (field1, field2)" | 
|  | * "unique index (indexname, field2)" | 
|  | * indexname is started by the first non '(' or ' ' | 
|  | *     after "unique index" | 
|  | * indexname is terminated by '(' ')' ' ' or ',' | 
|  | */ | 
|  | if (name_marker) { | 
|  | if (!udex_name && (temp[end] == '(' || | 
|  | temp[end] == ')' || | 
|  | temp[end] == ' ' || | 
|  | temp[end] == ',')) | 
|  | udex_name = xstrndup(name_marker, | 
|  | temp + end - name_marker); | 
|  | } else if (temp[end] != '(' && temp[end] != ' ') { | 
|  | name_marker = temp + end; | 
|  | } | 
|  |  | 
|  | /* find the end of the parenthetical expression */ | 
|  | if (temp[end] == '(') | 
|  | open++; | 
|  | else if (temp[end] == ')') | 
|  | close++; | 
|  | else | 
|  | continue; | 
|  | if (open == close) | 
|  | break; | 
|  | } | 
|  | if (temp[end]) { | 
|  | end++; | 
|  | unique_index = xstrndup(temp, end); | 
|  |  | 
|  | db_key = list_remove_first(keys_list, _find_db_key, | 
|  | udex_name); | 
|  | if (db_key) { | 
|  | xstrfmtcat(query, | 
|  | " drop index %s,", db_key->name); | 
|  | _destroy_db_key(db_key); | 
|  | } else { | 
|  | info("adding %s to table %s", | 
|  | unique_index, table_name); | 
|  | } | 
|  | xstrfmtcat(correct_query, " drop index %s,", udex_name); | 
|  | xstrfmtcat(query, " add %s,", unique_index); | 
|  | xstrfmtcat(correct_query, " add %s,", unique_index); | 
|  | xfree(unique_index); | 
|  | } | 
|  | xfree(udex_name); | 
|  | } | 
|  |  | 
|  | temp2 = ending; | 
|  | while ((temp = strstr(temp2, ", key "))) { | 
|  | int open = 0, close = 0, name_end = 0; | 
|  | int end = 5; | 
|  | char *new_key_name = NULL, *new_key = NULL; | 
|  | while (temp[end++]) { | 
|  | if (!name_end && (temp[end] == ' ')) { | 
|  | name_end = end; | 
|  | continue; | 
|  | } else if (temp[end] == '(') { | 
|  | open++; | 
|  | if (!name_end) | 
|  | name_end = end; | 
|  | } else if (temp[end] == ')') | 
|  | close++; | 
|  | else | 
|  | continue; | 
|  | if (open == close) | 
|  | break; | 
|  | } | 
|  | if (temp[end]) { | 
|  | end++; | 
|  | new_key_name = xstrndup(temp+6, name_end-6); | 
|  | new_key = xstrndup(temp+2, end-2); // skip ', ' | 
|  |  | 
|  | db_key = list_remove_first(keys_list, _find_db_key, | 
|  | new_key_name); | 
|  | if (db_key) { | 
|  | xstrfmtcat(query, | 
|  | " drop key %s,", db_key->name); | 
|  | _destroy_db_key(db_key); | 
|  | } else | 
|  | info("adding %s to table %s", | 
|  | new_key, table_name); | 
|  | xstrfmtcat(correct_query, | 
|  | " drop key %s,", new_key_name); | 
|  |  | 
|  | xstrfmtcat(query, " add %s,",  new_key); | 
|  | xstrfmtcat(correct_query, " add %s,",  new_key); | 
|  |  | 
|  | xfree(new_key); | 
|  | xfree(new_key_name); | 
|  | } | 
|  | temp2 = temp + end; | 
|  | } | 
|  |  | 
|  | /* flush extra (old) keys */ | 
|  | itr = list_iterator_create(keys_list); | 
|  | while ((db_key = list_next(itr))) { | 
|  | if (!db_key->non_unique) { | 
|  | info("dropping unique index %s from table %s", | 
|  | db_key->name, table_name); | 
|  | xstrfmtcat(query, " drop index %s,", db_key->name); | 
|  | } else { | 
|  | info("dropping key %s from table %s", | 
|  | db_key->name, table_name); | 
|  | xstrfmtcat(query, " drop key %s,", db_key->name); | 
|  | } | 
|  | } | 
|  | list_iterator_destroy(itr); | 
|  |  | 
|  | FREE_NULL_LIST(keys_list); | 
|  |  | 
|  | query[strlen(query)-1] = ';'; | 
|  | correct_query[strlen(correct_query)-1] = ';'; | 
|  | //info("%d query\n%s", __LINE__, query); | 
|  |  | 
|  | /* see if table needs to be altered after db server upgrade */ | 
|  | if (!adding && _alter_table_after_upgrade(mysql_conn, query)) { | 
|  | run_update = 3; | 
|  | } else if (!adding && !run_update) { | 
|  | /* see if we have already done this definition */ | 
|  | char *quoted = slurm_add_slash_to_quotes(query); | 
|  | char *query2 = xstrdup_printf("select table_name from " | 
|  | "%s where definition='%s'", | 
|  | table_defs_table, quoted); | 
|  | MYSQL_RES *result = NULL; | 
|  | MYSQL_ROW row; | 
|  |  | 
|  | xfree(quoted); | 
|  | run_update = 1; | 
|  | if ((result = mysql_db_query_ret(mysql_conn, query2, 0))) { | 
|  | if ((row = mysql_fetch_row(result))) | 
|  | run_update = 0; | 
|  | mysql_free_result(result); | 
|  | } | 
|  | xfree(query2); | 
|  | if (run_update) { | 
|  | run_update = 2; | 
|  | query2 = xstrdup_printf("select table_name from " | 
|  | "%s where table_name='%s'", | 
|  | table_defs_table, table_name); | 
|  | if ((result = mysql_db_query_ret( | 
|  | mysql_conn, query2, 0))) { | 
|  | if ((row = mysql_fetch_row(result))) | 
|  | run_update = 1; | 
|  | mysql_free_result(result); | 
|  | } | 
|  | xfree(query2); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* if something has changed run the alter line */ | 
|  | if (run_update || adding) { | 
|  | time_t now = time(NULL); | 
|  | char *query2 = NULL; | 
|  | char *quoted = NULL; | 
|  |  | 
|  | if (run_update == 2) | 
|  | debug4("Table %s doesn't exist, adding", table_name); | 
|  | else if (run_update == 3) | 
|  | info("MariaDB >= 10.2.1 in use with a table from an earlier version or from MySQL. Updating table %s...", | 
|  | table_name); | 
|  | else | 
|  | debug("Table %s has changed.  Updating...", table_name); | 
|  |  | 
|  | debug2("query\n%s", query); | 
|  | if (mysql_db_query(mysql_conn, query)) { | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | quoted = slurm_add_slash_to_quotes(correct_query); | 
|  | query2 = xstrdup_printf("insert into %s (creation_time, " | 
|  | "mod_time, table_name, definition) " | 
|  | "values (%ld, %ld, '%s', '%s') " | 
|  | "on duplicate key update " | 
|  | "definition='%s', mod_time=%ld;", | 
|  | table_defs_table, now, now, | 
|  | table_name, quoted, | 
|  | quoted, now); | 
|  | xfree(quoted); | 
|  | debug3("query\n%s", query2); | 
|  | if (mysql_db_query(mysql_conn, query2)) { | 
|  | xfree(query2); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query2); | 
|  | } | 
|  |  | 
|  | xfree(query); | 
|  | xfree(correct_query); | 
|  | query = xstrdup_printf("make table current %s", table_name); | 
|  | END_TIMER2(query); | 
|  | xfree(query); | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | void _set_mysql_ssl_opts(MYSQL *db_conn, const char *options) | 
|  | { | 
|  | char *tmp_opts, *token, *save_ptr = NULL; | 
|  | const char *key = NULL, *cert = NULL, *ca = NULL, *ca_path = NULL; | 
|  | const char *cipher = NULL; | 
|  |  | 
|  | if (!options) | 
|  | return; | 
|  |  | 
|  | tmp_opts = xstrdup(options); | 
|  | token = strtok_r(tmp_opts, ",", &save_ptr); | 
|  | while (token) { | 
|  | char *opt_str, *val_str = NULL; | 
|  |  | 
|  | opt_str = strtok_r(token, "=", &val_str); | 
|  |  | 
|  | if (!opt_str || !val_str) { | 
|  | error("Invalid storage option/val"); | 
|  | goto next; | 
|  | } else if (!xstrcasecmp(opt_str, "SSL_CERT")) | 
|  | cert = val_str; | 
|  | else if (!xstrcasecmp(opt_str, "SSL_CA")) | 
|  | ca = val_str; | 
|  | else if (!xstrcasecmp(opt_str, "SSL_CAPATH")) | 
|  | ca_path = val_str; | 
|  | else if (!xstrcasecmp(opt_str, "SSL_KEY")) | 
|  | key = val_str; | 
|  | else if (!xstrcasecmp(opt_str, "SSL_CIPHER")) | 
|  | cipher = val_str; | 
|  | else { | 
|  | error("Invalid storage option '%s'", opt_str); | 
|  | goto next; | 
|  | } | 
|  | next: | 
|  | token = strtok_r(NULL, ",", &save_ptr); | 
|  | } | 
|  |  | 
|  | mysql_ssl_set(db_conn, key, cert, ca, ca_path, cipher); | 
|  |  | 
|  | xfree(tmp_opts); | 
|  | } | 
|  |  | 
|  | /* NOTE: Ensure that mysql_conn->lock is set on function entry */ | 
|  | static int _create_db(char *db_name, mysql_db_info_t *db_info) | 
|  | { | 
|  | MYSQL *mysql_db = NULL; | 
|  | int rc = SLURM_ERROR; | 
|  |  | 
|  | MYSQL *db_ptr = NULL; | 
|  | char *db_host = NULL; | 
|  |  | 
|  | while (rc == SLURM_ERROR) { | 
|  | rc = SLURM_SUCCESS; | 
|  | if (!(mysql_db = mysql_init(mysql_db))) | 
|  | fatal("mysql_init failed: %s", mysql_error(mysql_db)); | 
|  |  | 
|  | _set_mysql_ssl_opts(mysql_db, db_info->params); | 
|  |  | 
|  | db_host = db_info->host; | 
|  | db_ptr = mysql_real_connect(mysql_db, | 
|  | db_host, db_info->user, | 
|  | db_info->pass, NULL, | 
|  | db_info->port, NULL, 0); | 
|  |  | 
|  | if (!db_ptr && db_info->backup) { | 
|  | info("Connection failed to host = %s " | 
|  | "user = %s port = %u", | 
|  | db_host, db_info->user, | 
|  | db_info->port); | 
|  | db_host = db_info->backup; | 
|  | db_ptr = mysql_real_connect(mysql_db, db_host, | 
|  | db_info->user, | 
|  | db_info->pass, NULL, | 
|  | db_info->port, NULL, 0); | 
|  | } | 
|  |  | 
|  | if (db_ptr) { | 
|  | char *create_line = NULL; | 
|  | xstrfmtcat(create_line, "create database %s", db_name); | 
|  | if (mysql_query(mysql_db, create_line)) { | 
|  | fatal("mysql_query failed: %d %s\n%s", | 
|  | mysql_errno(mysql_db), | 
|  | mysql_error(mysql_db), create_line); | 
|  | } | 
|  | xfree(create_line); | 
|  | if (mysql_thread_safe()) | 
|  | mysql_thread_end(); | 
|  | mysql_close(mysql_db); | 
|  | } else { | 
|  | info("Connection failed to host = %s " | 
|  | "user = %s port = %u", | 
|  | db_host, db_info->user, | 
|  | db_info->port); | 
|  | error("mysql_real_connect failed: %d %s", | 
|  | mysql_errno(mysql_db), | 
|  | mysql_error(mysql_db)); | 
|  | rc = SLURM_ERROR; | 
|  | } | 
|  | if (rc == SLURM_ERROR) | 
|  | sleep(3); | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern mysql_conn_t *create_mysql_conn(int conn_num, bool rollback, | 
|  | char *cluster_name) | 
|  | { | 
|  | mysql_conn_t *mysql_conn = xmalloc(sizeof(mysql_conn_t)); | 
|  |  | 
|  | if (rollback) | 
|  | mysql_conn->flags |= DB_CONN_FLAG_ROLLBACK; | 
|  | mysql_conn->conn = conn_num; | 
|  | mysql_conn->cluster_name = xstrdup(cluster_name); | 
|  | mysql_conn->wsrep_trx_fragment_size_orig = NO_VAL64; | 
|  | slurm_mutex_init(&mysql_conn->lock); | 
|  | mysql_conn->update_list = list_create(slurmdb_destroy_update_object); | 
|  |  | 
|  | return mysql_conn; | 
|  | } | 
|  |  | 
|  | extern int destroy_mysql_conn(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | if (mysql_conn) { | 
|  | mysql_db_close_db_connection(mysql_conn); | 
|  | xfree(mysql_conn->pre_commit_query); | 
|  | xfree(mysql_conn->cluster_name); | 
|  | slurm_mutex_destroy(&mysql_conn->lock); | 
|  | FREE_NULL_LIST(mysql_conn->update_list); | 
|  | xfree(mysql_conn->wsrep_trx_fragment_unit_orig); | 
|  | xfree(mysql_conn); | 
|  | } | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern mysql_db_info_t *create_mysql_db_info(slurm_mysql_plugin_type_t type) | 
|  | { | 
|  | mysql_db_info_t *db_info = xmalloc(sizeof(mysql_db_info_t)); | 
|  |  | 
|  | switch (type) { | 
|  | case SLURM_MYSQL_PLUGIN_AS: | 
|  | db_info->port = slurm_conf.accounting_storage_port; | 
|  | db_info->host = xstrdup(slurm_conf.accounting_storage_host); | 
|  | db_info->backup = | 
|  | xstrdup(slurm_conf.accounting_storage_backup_host); | 
|  | db_info->user = xstrdup(slurmdbd_conf->storage_user); | 
|  | db_info->pass = xstrdup(slurm_conf.accounting_storage_pass); | 
|  | db_info->params = xstrdup(slurm_conf.accounting_storage_params); | 
|  | break; | 
|  | case SLURM_MYSQL_PLUGIN_JC: | 
|  | if (!slurm_conf.job_comp_port) | 
|  | slurm_conf.job_comp_port = DEFAULT_MYSQL_PORT; | 
|  | db_info->port = slurm_conf.job_comp_port; | 
|  | db_info->host = xstrdup(slurm_conf.job_comp_host); | 
|  | db_info->user = xstrdup(slurm_conf.job_comp_user); | 
|  | db_info->pass = xstrdup(slurm_conf.job_comp_pass); | 
|  | db_info->params = xstrdup(slurm_conf.accounting_storage_params); | 
|  | break; | 
|  | default: | 
|  | xfree(db_info); | 
|  | fatal("Unknown mysql_db_info %d", type); | 
|  | } | 
|  | return db_info; | 
|  | } | 
|  |  | 
|  | extern int destroy_mysql_db_info(mysql_db_info_t *db_info) | 
|  | { | 
|  | if (db_info) { | 
|  | xfree(db_info->backup); | 
|  | xfree(db_info->host); | 
|  | xfree(db_info->user); | 
|  | xfree(db_info->pass); | 
|  | xfree(db_info); | 
|  | } | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_get_db_connection(mysql_conn_t *mysql_conn, char *db_name, | 
|  | mysql_db_info_t *db_info) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  | bool storage_init = false; | 
|  | char *db_host = db_info->host; | 
|  | unsigned int my_timeout = 30; | 
|  |  | 
|  | xassert(mysql_conn); | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  |  | 
|  | if (!(mysql_conn->db_conn = mysql_init(mysql_conn->db_conn))) { | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | fatal("mysql_init failed: %s", | 
|  | mysql_error(mysql_conn->db_conn)); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If this ever changes you will need to alter | 
|  | * src/common/slurmdbd_defs.c function _send_init_msg to | 
|  | * handle a different timeout when polling for the | 
|  | * response. | 
|  | */ | 
|  | mysql_options(mysql_conn->db_conn, MYSQL_OPT_CONNECT_TIMEOUT, | 
|  | (char *)&my_timeout); | 
|  |  | 
|  | _set_mysql_ssl_opts(mysql_conn->db_conn, db_info->params); | 
|  |  | 
|  | while (!storage_init) { | 
|  | debug2("Attempting to connect to %s:%d", db_host, | 
|  | db_info->port); | 
|  | if (!mysql_real_connect(mysql_conn->db_conn, db_host, | 
|  | db_info->user, db_info->pass, | 
|  | db_name, db_info->port, NULL, | 
|  | CLIENT_MULTI_STATEMENTS)) { | 
|  | const char *err_str = NULL; | 
|  | int err = mysql_errno(mysql_conn->db_conn); | 
|  |  | 
|  | if (err == ER_BAD_DB_ERROR) { | 
|  | debug("Database %s not created.  Creating", | 
|  | db_name); | 
|  | rc = _create_db(db_name, db_info); | 
|  |  | 
|  | /* | 
|  | * When using ca, cert and key the next | 
|  | * connect will fail. Setting the options again | 
|  | * fixes it. | 
|  | */ | 
|  | _set_mysql_ssl_opts(mysql_conn->db_conn, | 
|  | db_info->params); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | err_str = mysql_error(mysql_conn->db_conn); | 
|  |  | 
|  | if ((db_host == db_info->host) && db_info->backup) { | 
|  | debug2("mysql_real_connect failed: %d %s", | 
|  | err, err_str); | 
|  | db_host = db_info->backup; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | error("mysql_real_connect failed: %d %s", | 
|  | err, err_str); | 
|  | rc = ESLURM_DB_CONNECTION; | 
|  | mysql_close(mysql_conn->db_conn); | 
|  | mysql_conn->db_conn = NULL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | storage_init = true; | 
|  | if (mysql_conn->flags & DB_CONN_FLAG_ROLLBACK) | 
|  | mysql_autocommit(mysql_conn->db_conn, 0); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, | 
|  | "SET session sql_mode='ANSI_QUOTES," | 
|  | "NO_ENGINE_SUBSTITUTION';"); | 
|  | } | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | errno = rc; | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_close_db_connection(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | if (mysql_conn && mysql_conn->db_conn) { | 
|  | if (mysql_thread_safe()) | 
|  | mysql_thread_end(); | 
|  | mysql_close(mysql_conn->db_conn); | 
|  | mysql_conn->db_conn = NULL; | 
|  | } | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_cleanup(void) | 
|  | { | 
|  | debug3("starting mysql cleaning up"); | 
|  |  | 
|  | #ifdef mysql_library_end | 
|  | mysql_library_end(); | 
|  | #else | 
|  | mysql_server_end(); | 
|  | #endif | 
|  | debug3("finished mysql cleaning up"); | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_query(mysql_conn_t *mysql_conn, char *query) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  |  | 
|  | if (!mysql_conn || !mysql_conn->db_conn) { | 
|  | fatal("You haven't inited this storage yet."); | 
|  | return 0;	/* For CLANG false positive */ | 
|  | } | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, query); | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Executes a single delete sql query. | 
|  | * Returns the number of deleted rows, <0 for failure. | 
|  | */ | 
|  | extern int mysql_db_delete_affected_rows(mysql_conn_t *mysql_conn, char *query) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  |  | 
|  | if (!mysql_conn || !mysql_conn->db_conn) { | 
|  | fatal("You haven't inited this storage yet."); | 
|  | return 0;	/* For CLANG false positive */ | 
|  | } | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | if (!(rc = _mysql_query_internal(mysql_conn->db_conn, query))) | 
|  | rc = mysql_affected_rows(mysql_conn->db_conn); | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_ping(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | if (!mysql_conn->db_conn) | 
|  | return -1; | 
|  |  | 
|  | /* clear out the old results so we don't get a 2014 error */ | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | _clear_results(mysql_conn->db_conn); | 
|  | rc = mysql_ping(mysql_conn->db_conn); | 
|  | /* | 
|  | * Starting in MariaDB 10.2 many of the api commands started | 
|  | * setting errno erroneously. | 
|  | */ | 
|  | if (!rc) | 
|  | errno = 0; | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_commit(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  |  | 
|  | if (!mysql_conn->db_conn) | 
|  | return SLURM_ERROR; | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | /* clear out the old results so we don't get a 2014 error */ | 
|  | _clear_results(mysql_conn->db_conn); | 
|  | if (mysql_commit(mysql_conn->db_conn)) { | 
|  | error("mysql_commit failed: %d %s", | 
|  | mysql_errno(mysql_conn->db_conn), | 
|  | mysql_error(mysql_conn->db_conn)); | 
|  | errno = mysql_errno(mysql_conn->db_conn); | 
|  | rc = SLURM_ERROR; | 
|  | } | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_rollback(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  |  | 
|  | if (!mysql_conn->db_conn) | 
|  | return SLURM_ERROR; | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | /* clear out the old results so we don't get a 2014 error */ | 
|  | _clear_results(mysql_conn->db_conn); | 
|  | if (mysql_rollback(mysql_conn->db_conn)) { | 
|  | error("mysql_commit failed: %d %s", | 
|  | mysql_errno(mysql_conn->db_conn), | 
|  | mysql_error(mysql_conn->db_conn)); | 
|  | errno = mysql_errno(mysql_conn->db_conn); | 
|  | rc = SLURM_ERROR; | 
|  | } else { | 
|  | /* | 
|  | * Starting in MariaDB 10.2 many of the api commands started | 
|  | * setting errno erroneously. | 
|  | */ | 
|  | errno = 0; | 
|  | } | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  |  | 
|  | } | 
|  |  | 
|  | extern MYSQL_RES *mysql_db_query_ret(mysql_conn_t *mysql_conn, | 
|  | char *query, bool last) | 
|  | { | 
|  | MYSQL_RES *result = NULL; | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | if (_mysql_query_internal(mysql_conn->db_conn, query) != SLURM_ERROR)  { | 
|  | if (mysql_errno(mysql_conn->db_conn) == ER_NO_SUCH_TABLE) | 
|  | goto fini; | 
|  | else if (last) | 
|  | result = _get_last_result(mysql_conn->db_conn); | 
|  | else | 
|  | result = _get_first_result(mysql_conn->db_conn); | 
|  | /* | 
|  | * Starting in MariaDB 10.2 many of the api commands started | 
|  | * setting errno erroneously. | 
|  | */ | 
|  | errno = 0; | 
|  | if (!result && mysql_field_count(mysql_conn->db_conn)) { | 
|  | /* should have returned data */ | 
|  | error("We should have gotten a result: '%m' '%s'", | 
|  | mysql_error(mysql_conn->db_conn)); | 
|  | } | 
|  | } | 
|  |  | 
|  | fini: | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_query_check_after(mysql_conn_t *mysql_conn, char *query) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | if ((rc = _mysql_query_internal( | 
|  | mysql_conn->db_conn, query)) != SLURM_ERROR) | 
|  | rc = _clear_results(mysql_conn->db_conn); | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern uint64_t mysql_db_insert_ret_id(mysql_conn_t *mysql_conn, char *query) | 
|  | { | 
|  | uint64_t new_id = 0; | 
|  |  | 
|  | slurm_mutex_lock(&mysql_conn->lock); | 
|  | if (_mysql_query_internal(mysql_conn->db_conn, query) != SLURM_ERROR)  { | 
|  | new_id = mysql_insert_id(mysql_conn->db_conn); | 
|  | if (!new_id) { | 
|  | /* should have new id */ | 
|  | error("%s: We should have gotten a new id: %s", | 
|  | __func__, mysql_error(mysql_conn->db_conn)); | 
|  | } | 
|  | } | 
|  | slurm_mutex_unlock(&mysql_conn->lock); | 
|  | return new_id; | 
|  |  | 
|  | } | 
|  |  | 
|  | extern int mysql_db_create_table(mysql_conn_t *mysql_conn, char *table_name, | 
|  | storage_field_t *fields, char *ending) | 
|  | { | 
|  | char *query = NULL; | 
|  | int rc; | 
|  | storage_field_t *first_field = fields; | 
|  |  | 
|  | if (!fields || !fields->name) { | 
|  | error("Not creating an empty table"); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  |  | 
|  | /* We have an internal table called table_defs_table which | 
|  | * contains the definition of each table in the database.  To | 
|  | * speed things up we just check against that to see if | 
|  | * anything has changed. | 
|  | */ | 
|  | query = xstrdup_printf("create table if not exists %s " | 
|  | "(creation_time int unsigned not null, " | 
|  | "mod_time int unsigned default 0 not null, " | 
|  | "table_name text not null, " | 
|  | "definition text not null, " | 
|  | "primary key (table_name(50))) engine='innodb'", | 
|  | table_defs_table); | 
|  | if (mysql_db_query(mysql_conn, query) == SLURM_ERROR) { | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query); | 
|  |  | 
|  | query = xstrdup_printf("create table if not exists %s (`%s` %s", | 
|  | table_name, fields->name, fields->options); | 
|  | fields++; | 
|  |  | 
|  | while (fields && fields->name) { | 
|  | xstrfmtcat(query, ", `%s` %s", fields->name, fields->options); | 
|  | fields++; | 
|  | } | 
|  | xstrcat(query, ending); | 
|  |  | 
|  | /* make sure we can do a rollback */ | 
|  | xstrcat(query, " engine='innodb'"); | 
|  |  | 
|  | if (mysql_db_query(mysql_conn, query) == SLURM_ERROR) { | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(query); | 
|  |  | 
|  | rc = _mysql_make_table_current( | 
|  | mysql_conn, table_name, first_field, ending); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_get_var_str(mysql_conn_t *mysql_conn, | 
|  | const char *variable_name, | 
|  | char **value) | 
|  | { | 
|  | MYSQL_ROW row = NULL; | 
|  | MYSQL_RES *result = NULL; | 
|  | char *query; | 
|  |  | 
|  | query = xstrdup_printf("select @@%s;", variable_name); | 
|  | result = mysql_db_query_ret(mysql_conn, query, 0); | 
|  | if (!result) { | 
|  | error("%s: null result from query `%s`", __func__, query); | 
|  | xfree(query); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  |  | 
|  | if (mysql_num_rows(result) != 1) { | 
|  | error("%s: invalid results from query `%s`", __func__, query); | 
|  | xfree(query); | 
|  | mysql_free_result(result); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  |  | 
|  | xfree(query); | 
|  |  | 
|  | row = mysql_fetch_row(result); | 
|  | *value = xstrdup(row[0]); | 
|  |  | 
|  | mysql_free_result(result); | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern int mysql_db_get_var_u64(mysql_conn_t *mysql_conn, | 
|  | const char *variable_name, | 
|  | uint64_t *value) | 
|  | { | 
|  | char *err_check = NULL, *var_str = NULL; | 
|  |  | 
|  | if (mysql_db_get_var_str(mysql_conn, variable_name, &var_str)) { | 
|  | return SLURM_ERROR; | 
|  | } | 
|  |  | 
|  | *value = strtoull(var_str, &err_check, 10); | 
|  |  | 
|  | if (*err_check) { | 
|  | error("%s: error parsing string to int `%s`", | 
|  | __func__, var_str); | 
|  | xfree(var_str); | 
|  | return SLURM_ERROR; | 
|  | } | 
|  | xfree(var_str); | 
|  |  | 
|  | return SLURM_SUCCESS; | 
|  | } | 
|  |  | 
|  | extern void mysql_db_enable_streaming_replication(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | int rc = SLURM_SUCCESS; | 
|  | char *query; | 
|  | uint64_t wsrep_on, wsrep_max_ws_size, fragment_size; | 
|  |  | 
|  | /* if this errors, assume wsrep_on doesn't exist, so must be disabled */ | 
|  | if (mysql_db_get_var_u64(mysql_conn, "wsrep_on", &wsrep_on)) { | 
|  | wsrep_on = 0; | 
|  | if (errno == ER_UNKNOWN_SYSTEM_VARIABLE) | 
|  | error("The prior error message regarding an undefined 'wsrep_on' variable is innocuous.  MySQL and MariaDB < 10.1 do not have this variable and Slurm will operate normally without it."); | 
|  | } | 
|  |  | 
|  | debug2("wsrep_on=%"PRIu64, wsrep_on); | 
|  |  | 
|  | if (!wsrep_on) | 
|  | return; | 
|  |  | 
|  | /* | 
|  | * wsrep_max_ws_size represents the maximum write set size in bytes. | 
|  | * The fragment cannot exceed this value. | 
|  | */ | 
|  | rc = mysql_db_get_var_u64(mysql_conn, "wsrep_max_ws_size", | 
|  | &wsrep_max_ws_size); | 
|  | if (rc) { | 
|  | error("Failed to get wsrep_max_ws_size"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Save the initial wsrep settings so they can be restored later. | 
|  | * If these were set previously, don't set them again. | 
|  | * | 
|  | * If these variables don't exist, streaming replication isn't supported | 
|  | * so don't turn it on. | 
|  | */ | 
|  | if (!mysql_conn->wsrep_trx_fragment_unit_orig) { | 
|  | rc = mysql_db_get_var_str( | 
|  | mysql_conn, | 
|  | "wsrep_trx_fragment_unit", | 
|  | &mysql_conn->wsrep_trx_fragment_unit_orig); | 
|  | if (rc) { | 
|  | if (errno == ER_UNKNOWN_SYSTEM_VARIABLE) | 
|  | error("This version of galera does not support streaming replication."); | 
|  | error("Unable to fetch wsrep_trx_fragment_unit."); | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (mysql_conn->wsrep_trx_fragment_size_orig == NO_VAL64) { | 
|  | rc = mysql_db_get_var_u64( | 
|  | mysql_conn, | 
|  | "wsrep_trx_fragment_size", | 
|  | &mysql_conn->wsrep_trx_fragment_size_orig); | 
|  | if (rc) { | 
|  | if (errno == ER_UNKNOWN_SYSTEM_VARIABLE) | 
|  | error("This version of galera does not support streaming replication."); | 
|  | error("Unable to fetch wsrep_trx_fragment_size."); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Force the wsrep_trx_fragment_unit to bytes. The default may change | 
|  | * in the future, or may have been set by the site, so don't rely on it | 
|  | * being a specific value. | 
|  | */ | 
|  | query = xstrdup("SET @@SESSION.wsrep_trx_fragment_unit=\'bytes\';"); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, query); | 
|  | xfree(query); | 
|  | if (rc) { | 
|  | error("Unable to set wsrep_trx_fragment_unit."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Set the fragment size to 128MiB, or wsrep_max_ws_size if it has been | 
|  | * set below that. Simply setting it to the max size does not strictly | 
|  | * result in the best performance. | 
|  | */ | 
|  | fragment_size = MIN(wsrep_max_ws_size, 134217700); | 
|  | query = xstrdup_printf("SET @@SESSION.wsrep_trx_fragment_size=%"PRIu64";", | 
|  | fragment_size); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, query); | 
|  | xfree(query); | 
|  | if (rc) | 
|  | error("Failed to set wsrep_trx_fragment_size"); | 
|  | else | 
|  | debug2("set wsrep_trx_fragment_size=%"PRIu64" bytes", | 
|  | fragment_size); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | extern void mysql_db_restore_streaming_replication(mysql_conn_t *mysql_conn) | 
|  | { | 
|  | int rc; | 
|  | char *query; | 
|  |  | 
|  | /* | 
|  | * Check if the connection has saved streaming replication settings.  If | 
|  | * not there is nothing to restore. | 
|  | */ | 
|  | if (!mysql_conn->wsrep_trx_fragment_unit_orig && | 
|  | (mysql_conn->wsrep_trx_fragment_size_orig == NO_VAL64)) { | 
|  | debug2("no streaming replication settings to restore"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (mysql_conn->wsrep_trx_fragment_unit_orig) { | 
|  | query = xstrdup_printf( | 
|  | "SET @@SESSION.wsrep_trx_fragment_unit=\'%s\';", | 
|  | mysql_conn->wsrep_trx_fragment_unit_orig); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, query); | 
|  | xfree(query); | 
|  | if (rc) { | 
|  | error("Unable to restore wsrep_trx_fragment_unit."); | 
|  | } else { | 
|  | debug2("Restored wsrep_trx_fragment_unit=%s", | 
|  | mysql_conn->wsrep_trx_fragment_unit_orig); | 
|  | xfree(mysql_conn->wsrep_trx_fragment_unit_orig); | 
|  | } | 
|  | } | 
|  | if (mysql_conn->wsrep_trx_fragment_size_orig != NO_VAL64) { | 
|  | query = xstrdup_printf( | 
|  | "SET @@SESSION.wsrep_trx_fragment_size=%"PRIu64";", | 
|  | mysql_conn->wsrep_trx_fragment_size_orig); | 
|  | rc = _mysql_query_internal(mysql_conn->db_conn, query); | 
|  | xfree(query); | 
|  | if (rc) { | 
|  | error("Unable to restore wsrep_trx_fragment_size."); | 
|  | } else { | 
|  | debug2("Restored wsrep_trx_fragment_size=%"PRIu64, | 
|  | mysql_conn->wsrep_trx_fragment_size_orig); | 
|  | mysql_conn->wsrep_trx_fragment_size_orig = NO_VAL64; | 
|  | } | 
|  | } | 
|  |  | 
|  | return; | 
|  | } |