*/
#include "includes.h"
-#include "lib/tevent/tevent.h"
-#include "lib/tdb/include/tdb.h"
+#include "tdb.h"
#include "system/network.h"
#include "system/filesys.h"
#include "system/dir.h"
#include "system/time.h"
#include "../include/ctdb_private.h"
#include "../common/rb_tree.h"
-#include "db_wrap.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
#include "lib/util/dlinklist.h"
#include <ctype.h>
bool seqnum_suppressed = false;
bool keep = false;
bool schedule_for_deletion = false;
+ bool remove_from_delete_queue = false;
uint32_t lmaster;
if (ctdb->flags & CTDB_FLAG_TORTURE) {
*/
if (data.dsize != 0) {
keep = true;
+ } else if (header->flags & CTDB_REC_RO_FLAGS) {
+ keep = true;
} else if (ctdb_db->persistent) {
keep = true;
} else if (header->flags & CTDB_REC_FLAG_AUTOMATIC) {
* fails. So storing the empty record makes sure that we do not
* need to change the client code.
*/
+ if ((header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) &&
+ (ctdb_db->ctdb->pnn == header->dmaster)) {
+ keep = true;
+ schedule_for_deletion = true;
+ }
if (!(header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED)) {
keep = true;
} else if (ctdb_db->ctdb->pnn != header->dmaster) {
keep = true;
}
- if (keep &&
- (data.dsize == 0) &&
- !ctdb_db->persistent &&
- (ctdb_db->ctdb->pnn == header->dmaster))
- {
- schedule_for_deletion = true;
+ if (keep) {
+ if (!ctdb_db->persistent &&
+ (ctdb_db->ctdb->pnn == header->dmaster) &&
+ !(header->flags & CTDB_REC_RO_FLAGS))
+ {
+ header->rsn++;
+
+ if (data.dsize == 0) {
+ schedule_for_deletion = true;
+ }
+ }
+ remove_from_delete_queue = !schedule_for_deletion;
}
store:
tdb_errorstr(ctdb_db->ltdb->tdb)));
schedule_for_deletion = false;
+ remove_from_delete_queue = false;
}
if (seqnum_suppressed) {
tdb_add_flags(ctdb_db->ltdb->tdb, TDB_SEQNUM);
}
}
+ if (remove_from_delete_queue) {
+ ctdb_local_remove_from_delete_queue(ctdb_db, header, key);
+ }
+
return ret;
}
/*
called when we should retry the operation
*/
-static void lock_fetch_callback(void *p)
+static void lock_fetch_callback(void *p, bool locked)
{
struct lock_fetch_state *state = talloc_get_type(p, struct lock_fetch_state);
if (!state->ignore_generation &&
1) tries to get the chainlock. If it succeeds, then it returns 0
2) if it fails to get a chainlock immediately then it sets up a
- non-blocking chainlock via ctdb_lockwait, and when it gets the
+ non-blocking chainlock via ctdb_lock_record, and when it gets the
chainlock it re-submits this ctdb request to the main packet
- receive function
+ receive function.
This effectively queues all ctdb requests that cannot be
immediately satisfied until it can get the lock. This means that
{
int ret;
struct tdb_context *tdb = ctdb_db->ltdb->tdb;
- struct lockwait_handle *h;
+ struct lock_request *lreq;
struct lock_fetch_state *state;
ret = tdb_chainlock_nonblock(tdb, key);
state->ignore_generation = ignore_generation;
/* now the contended path */
- h = ctdb_lockwait(ctdb_db, key, lock_fetch_callback, state);
- if (h == NULL) {
+ lreq = ctdb_lock_record(state, ctdb_db, key, true, lock_fetch_callback, state);
+ if (lreq == NULL) {
return -1;
}
/* we need to move the packet off the temporary context in ctdb_input_pkt(),
so it won't be freed yet */
talloc_steal(state, hdr);
- talloc_steal(state, h);
/* now tell the caller than we will retry asynchronously */
return -2;
return -1;
}
- if (may_recover && !ctdb->done_startup) {
+ if (may_recover && ctdb->runstate == CTDB_RUNSTATE_STARTUP) {
DEBUG(DEBUG_ERR, (__location__ " db %s become healthy - force recovery for startup\n",
ctdb_db->db_name));
ctdb->recovery_mode = CTDB_RECOVERY_ACTIVE;
}
if (ctdb_db->persistent) {
- DEBUG(DEBUG_ERR,("Trying to set persistent database with readonly property\n"));
+ DEBUG(DEBUG_ERR,("Persistent databases do not support readonly property\n"));
return -1;
}
DEBUG(DEBUG_NOTICE,("OPENED tracking database : '%s'\n", ropath));
ctdb_db->readonly = true;
+
+ DEBUG(DEBUG_NOTICE, ("Readonly property set on DB %s\n", ctdb_db->db_name));
+
talloc_free(ropath);
return 0;
}
*/
static int ctdb_local_attach(struct ctdb_context *ctdb, const char *db_name,
bool persistent, const char *unhealthy_reason,
- bool jenkinshash)
+ bool jenkinshash, bool mutexes)
{
struct ctdb_db_context *ctdb_db, *tmp_db;
int ret;
if (ctdb->max_persistent_check_errors > 0) {
remaining_tries = 1;
}
- if (ctdb->done_startup) {
+ if (ctdb->runstate == CTDB_RUNSTATE_RUNNING) {
remaining_tries = 0;
}
if (jenkinshash) {
tdb_flags |= TDB_INCOMPATIBLE_HASH;
}
+#ifdef TDB_MUTEX_LOCKING
+ if (ctdb->tunable.mutex_enabled && mutexes &&
+ tdb_runtime_check_for_robust_mutexes()) {
+ tdb_flags |= (TDB_MUTEX_LOCKING | TDB_CLEAR_IF_FIRST);
+ }
+#endif
again:
- ctdb_db->ltdb = tdb_wrap_open(ctdb, ctdb_db->db_path,
+ ctdb_db->ltdb = tdb_wrap_open(ctdb_db, ctdb_db->db_path,
ctdb->tunable.database_hash_size,
tdb_flags,
O_CREAT|O_RDWR, mode);
return -1;
}
+ ctdb_db->defer_dmaster = trbt_create(ctdb_db, 0);
+ if (ctdb_db->defer_dmaster == NULL) {
+ DEBUG(DEBUG_ERR, ("Failed to create defer dmaster rb tree for %s\n",
+ ctdb_db->db_name));
+ talloc_free(ctdb_db);
+ return -1;
+ }
+
DLIST_ADD(ctdb->db_list, ctdb_db);
/* setting this can help some high churn databases */
}
- DEBUG(DEBUG_INFO,("Attached to database '%s'\n", ctdb_db->db_path));
-
+ DEBUG(DEBUG_NOTICE,("Attached to database '%s' with flags 0x%x\n",
+ ctdb_db->db_path, tdb_flags));
+
/* success */
return 0;
}
struct ctdb_db_context *db;
struct ctdb_node *node = ctdb->nodes[ctdb->pnn];
struct ctdb_client *client = NULL;
+ bool with_jenkinshash, with_mutexes;
if (ctdb->tunable.allow_client_db_attach == 0) {
DEBUG(DEBUG_ERR, ("DB Attach to database %s denied by tunable "
databases
*/
if (node->flags & NODE_FLAGS_INACTIVE) {
- DEBUG(DEBUG_ERR,("DB Attach to database %s refused since node is inactive (disconnected or banned)\n", db_name));
+ DEBUG(DEBUG_ERR,("DB Attach to database %s refused since node is inactive (flags=0x%x)\n", db_name, node->flags));
return -1;
}
- if (ctdb->recovery_mode == CTDB_RECOVERY_ACTIVE
- && client->pid != ctdb->recoverd_pid
- && !ctdb->done_startup) {
+ if (ctdb->recovery_mode == CTDB_RECOVERY_ACTIVE &&
+ client->pid != ctdb->recoverd_pid &&
+ ctdb->runstate < CTDB_RUNSTATE_RUNNING) {
struct ctdb_deferred_attach_context *da_ctx = talloc(client, struct ctdb_deferred_attach_context);
if (da_ctx == NULL) {
only allow a subset of those on the database in ctdb. Note
that tdb_flags is passed in via the (otherwise unused)
srvid to the attach control */
+#ifdef TDB_MUTEX_LOCKING
+ tdb_flags &= (TDB_NOSYNC|TDB_INCOMPATIBLE_HASH|TDB_MUTEX_LOCKING|TDB_CLEAR_IF_FIRST);
+#else
tdb_flags &= (TDB_NOSYNC|TDB_INCOMPATIBLE_HASH);
+#endif
/* see if we already have this name */
db = ctdb_db_handle(ctdb, db_name);
if (db) {
+ if (db->persistent != persistent) {
+ DEBUG(DEBUG_ERR, ("ERROR: DB Attach %spersistent to %spersistent "
+ "database %s\n", persistent ? "" : "non-",
+ db-> persistent ? "" : "non-", db_name));
+ return -1;
+ }
outdata->dptr = (uint8_t *)&db->db_id;
outdata->dsize = sizeof(db->db_id);
tdb_add_flags(db->ltdb->tdb, tdb_flags);
return 0;
}
- if (ctdb_local_attach(ctdb, db_name, persistent, NULL, (tdb_flags&TDB_INCOMPATIBLE_HASH)?true:false) != 0) {
+ with_jenkinshash = (tdb_flags & TDB_INCOMPATIBLE_HASH) ? true : false;
+#ifdef TDB_MUTEX_LOCKING
+ with_mutexes = (tdb_flags & TDB_MUTEX_LOCKING) ? true : false;
+#else
+ with_mutexes = false;
+#endif
+
+ if (ctdb_local_attach(ctdb, db_name, persistent, NULL,
+ with_jenkinshash, with_mutexes) != 0) {
return -1;
}
outdata->dsize = sizeof(db->db_id);
/* Try to ensure it's locked in mem */
- ctdb_lockdown_memory(ctdb);
+ lockdown_memory(ctdb->valgrinding);
/* tell all the other nodes about this database */
ctdb_daemon_send_control(ctdb, CTDB_BROADCAST_ALL, tdb_flags,
return 0;
}
+/*
+ * a client has asked to detach from a database
+ */
+int32_t ctdb_control_db_detach(struct ctdb_context *ctdb, TDB_DATA indata,
+ uint32_t client_id)
+{
+ uint32_t db_id;
+ struct ctdb_db_context *ctdb_db;
+ struct ctdb_client *client = NULL;
+
+ db_id = *(uint32_t *)indata.dptr;
+ ctdb_db = find_ctdb_db(ctdb, db_id);
+ if (ctdb_db == NULL) {
+ DEBUG(DEBUG_ERR, ("Invalid dbid 0x%08x in DB detach\n",
+ db_id));
+ return -1;
+ }
+
+ if (ctdb->tunable.allow_client_db_attach == 1) {
+ DEBUG(DEBUG_ERR, ("DB detach from database %s denied. "
+ "Clients are allowed access to databases "
+ "(AllowClientDBAccess == 1)\n",
+ ctdb_db->db_name));
+ return -1;
+ }
+
+ if (ctdb_db->persistent) {
+ DEBUG(DEBUG_ERR, ("DB detach from persistent database %s "
+ "denied\n", ctdb_db->db_name));
+ return -1;
+ }
+
+ /* Cannot detach from database when in recovery */
+ if (ctdb->recovery_mode == CTDB_RECOVERY_ACTIVE) {
+ DEBUG(DEBUG_ERR, ("DB detach denied while in recovery\n"));
+ return -1;
+ }
+
+ /* If a control comes from a client, then broadcast it to all nodes.
+ * Do the actual detach only if the control comes from other daemons.
+ */
+ if (client_id != 0) {
+ client = ctdb_reqid_find(ctdb, client_id, struct ctdb_client);
+ if (client != NULL) {
+ /* forward the control to all the nodes */
+ ctdb_daemon_send_control(ctdb, CTDB_BROADCAST_ALL, 0,
+ CTDB_CONTROL_DB_DETACH, 0,
+ CTDB_CTRL_FLAG_NOREPLY,
+ indata, NULL, NULL);
+ return 0;
+ }
+ DEBUG(DEBUG_ERR, ("Client has gone away. Failing DB detach "
+ "for database '%s'\n", ctdb_db->db_name));
+ return -1;
+ }
+
+ /* Detach database from recoverd */
+ if (ctdb_daemon_send_message(ctdb, ctdb->pnn,
+ CTDB_SRVID_DETACH_DATABASE,
+ indata) != 0) {
+ DEBUG(DEBUG_ERR, ("Unable to detach DB from recoverd\n"));
+ return -1;
+ }
+
+ /* Disable vacuuming and drop all vacuuming data */
+ talloc_free(ctdb_db->vacuum_handle);
+ talloc_free(ctdb_db->delete_queue);
+
+ /* Terminate any deferred fetch */
+ talloc_free(ctdb_db->deferred_fetch);
+
+ /* Terminate any traverses */
+ while (ctdb_db->traverse) {
+ talloc_free(ctdb_db->traverse);
+ }
+
+ /* Terminate any revokes */
+ while (ctdb_db->revokechild_active) {
+ talloc_free(ctdb_db->revokechild_active);
+ }
+
+ /* Free readonly tracking database */
+ if (ctdb_db->readonly) {
+ talloc_free(ctdb_db->rottdb);
+ }
+
+ DLIST_REMOVE(ctdb->db_list, ctdb_db);
+
+ DEBUG(DEBUG_NOTICE, ("Detached from database '%s'\n",
+ ctdb_db->db_name));
+ talloc_free(ctdb_db);
+
+ return 0;
+}
/*
attach to all existing persistent databases
int invalid_name = 0;
s = talloc_strdup(ctdb, de->d_name);
- CTDB_NO_MEMORY(ctdb, s);
+ if (s == NULL) {
+ closedir(d);
+ CTDB_NO_MEMORY(ctdb, s);
+ }
/* only accept names ending in .tdb */
p = strstr(s, ".tdb.");
}
p[4] = 0;
- if (ctdb_local_attach(ctdb, s, true, unhealthy_reason, 0) != 0) {
+ if (ctdb_local_attach(ctdb, s, true, unhealthy_reason, false, false) != 0) {
DEBUG(DEBUG_ERR,("Failed to attach to persistent database '%s'\n", de->d_name));
closedir(d);
talloc_free(s);
char *unhealthy_reason = NULL;
bool first_try = true;
- if (ctdb->db_directory == NULL) {
- ctdb->db_directory = VARDIR "/ctdb";
- }
- if (ctdb->db_directory_persistent == NULL) {
- ctdb->db_directory_persistent = VARDIR "/ctdb/persistent";
- }
- if (ctdb->db_directory_state == NULL) {
- ctdb->db_directory_state = VARDIR "/ctdb/state";
- }
-
- /* make sure the db directory exists */
- ret = mkdir(ctdb->db_directory, 0700);
- if (ret == -1 && errno != EEXIST) {
- DEBUG(DEBUG_CRIT,(__location__ " Unable to create ctdb directory '%s'\n",
- ctdb->db_directory));
- return -1;
- }
-
- /* make sure the persistent db directory exists */
- ret = mkdir(ctdb->db_directory_persistent, 0700);
- if (ret == -1 && errno != EEXIST) {
- DEBUG(DEBUG_CRIT,(__location__ " Unable to create ctdb persistent directory '%s'\n",
- ctdb->db_directory_persistent));
- return -1;
- }
-
- /* make sure the internal state db directory exists */
- ret = mkdir(ctdb->db_directory_state, 0700);
- if (ret == -1 && errno != EEXIST) {
- DEBUG(DEBUG_CRIT,(__location__ " Unable to create ctdb state directory '%s'\n",
- ctdb->db_directory_state));
- return -1;
- }
-
persistent_health_path = talloc_asprintf(ctdb, "%s/%s.%u",
ctdb->db_directory_state,
PERSISTENT_HEALTH_TDB,
return 0;
}
-int32_t ctdb_control_set_db_priority(struct ctdb_context *ctdb, TDB_DATA indata)
+int32_t ctdb_control_set_db_priority(struct ctdb_context *ctdb, TDB_DATA indata,
+ uint32_t client_id)
{
struct ctdb_db_priority *db_prio = (struct ctdb_db_priority *)indata.dptr;
struct ctdb_db_context *ctdb_db;
ctdb_db = find_ctdb_db(ctdb, db_prio->db_id);
if (!ctdb_db) {
- DEBUG(DEBUG_ERR,("Unknown db_id 0x%x in ctdb_set_db_priority\n", db_prio->db_id));
+ if (!(ctdb->nodes[ctdb->pnn]->flags & NODE_FLAGS_INACTIVE)) {
+ DEBUG(DEBUG_ERR,("Unknown db_id 0x%x in ctdb_set_db_priority\n",
+ db_prio->db_id));
+ }
return 0;
}
ctdb_db->priority = db_prio->priority;
DEBUG(DEBUG_INFO,("Setting DB priority to %u for db 0x%08x\n", db_prio->priority, db_prio->db_id));
+ if (client_id != 0) {
+ /* Broadcast the update to the rest of the cluster */
+ ctdb_daemon_send_control(ctdb, CTDB_BROADCAST_ALL, 0,
+ CTDB_CONTROL_SET_DB_PRIORITY, 0,
+ CTDB_CTRL_FLAG_NOREPLY, indata,
+ NULL, NULL);
+ }
return 0;
}
+
+int ctdb_set_db_sticky(struct ctdb_context *ctdb, struct ctdb_db_context *ctdb_db)
+{
+ if (ctdb_db->sticky) {
+ return 0;
+ }
+
+ if (ctdb_db->persistent) {
+ DEBUG(DEBUG_ERR,("Trying to set persistent database with sticky property\n"));
+ return -1;
+ }
+
+ ctdb_db->sticky_records = trbt_create(ctdb_db, 0);
+
+ ctdb_db->sticky = true;
+
+ DEBUG(DEBUG_NOTICE,("set db sticky %s\n", ctdb_db->db_name));
+
+ return 0;
+}
+
+int32_t ctdb_control_get_db_statistics(struct ctdb_context *ctdb,
+ uint32_t db_id,
+ TDB_DATA *outdata)
+{
+ struct ctdb_db_context *ctdb_db;
+ struct ctdb_db_statistics *stats;
+ int i;
+ int len;
+ char *ptr;
+
+ ctdb_db = find_ctdb_db(ctdb, db_id);
+ if (!ctdb_db) {
+ DEBUG(DEBUG_ERR,("Unknown db_id 0x%x in get_db_statistics\n", db_id));
+ return -1;
+ }
+
+ len = offsetof(struct ctdb_db_statistics, hot_keys_wire);
+ for (i = 0; i < MAX_HOT_KEYS; i++) {
+ len += ctdb_db->statistics.hot_keys[i].key.dsize;
+ }
+
+ stats = talloc_size(outdata, len);
+ if (stats == NULL) {
+ DEBUG(DEBUG_ERR,("Failed to allocate db statistics structure\n"));
+ return -1;
+ }
+
+ *stats = ctdb_db->statistics;
+
+ stats->num_hot_keys = MAX_HOT_KEYS;
+
+ ptr = &stats->hot_keys_wire[0];
+ for (i = 0; i < MAX_HOT_KEYS; i++) {
+ memcpy(ptr, ctdb_db->statistics.hot_keys[i].key.dptr,
+ ctdb_db->statistics.hot_keys[i].key.dsize);
+ ptr += ctdb_db->statistics.hot_keys[i].key.dsize;
+ }
+
+ outdata->dptr = (uint8_t *)stats;
+ outdata->dsize = len;
+
+ return 0;
+}