1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-01 05:47:28 +03:00

ctdb-common: Add db_hash abstraction

A hash table implemented using in-memory tdb backend.

Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
This commit is contained in:
Amitay Isaacs 2015-03-17 12:35:31 +11:00 committed by Amitay Isaacs
parent acf5ebfa90
commit e5592f9fc0
6 changed files with 556 additions and 1 deletions

268
ctdb/common/db_hash.c Normal file
View File

@ -0,0 +1,268 @@
/*
Using tdb as a hash table
Copyright (C) Amitay Isaacs 2015
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "replace.h"
#include "system/filesys.h"
#include <talloc.h>
#include <tdb.h>
#include "common/db_hash.h"
struct db_hash_context {
struct tdb_context *db;
};
static int db_hash_destructor(struct db_hash_context *dh)
{
if (dh->db != NULL) {
tdb_close(dh->db);
dh->db = NULL;
}
return 0;
}
int db_hash_init(TALLOC_CTX *mem_ctx, const char *name, int hash_size,
enum db_hash_type type, struct db_hash_context **result)
{
struct db_hash_context *dh;
int tdb_flags = TDB_INTERNAL | TDB_DISALLOW_NESTING;
dh = talloc_zero(mem_ctx, struct db_hash_context);
if (dh == NULL) {
return ENOMEM;
}
if (type == DB_HASH_COMPLEX) {
tdb_flags |= TDB_INCOMPATIBLE_HASH;
}
dh->db = tdb_open(name, hash_size, tdb_flags, O_RDWR|O_CREAT, 0);
if (dh->db == NULL) {
talloc_free(dh);
return ENOMEM;
}
talloc_set_destructor(dh, db_hash_destructor);
*result = dh;
return 0;
}
static int db_hash_map_tdb_error(struct db_hash_context *dh)
{
enum TDB_ERROR tdb_err;
int ret;
tdb_err = tdb_error(dh->db);
switch (tdb_err) {
case TDB_SUCCESS:
ret = 0; break;
case TDB_ERR_OOM:
ret = ENOMEM; break;
case TDB_ERR_EXISTS:
ret = EEXIST; break;
case TDB_ERR_NOEXIST:
ret = ENOENT; break;
case TDB_ERR_EINVAL:
ret = EINVAL; break;
default:
ret = EIO; break;
}
return ret;
}
int db_hash_insert(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
uint8_t *databuf, size_t datalen)
{
TDB_DATA key, data;
int ret;
if (dh == NULL) {
return EINVAL;
}
key.dptr = keybuf;
key.dsize = keylen;
data.dptr = databuf;
data.dsize = datalen;
ret = tdb_store(dh->db, key, data, TDB_INSERT);
if (ret != 0) {
ret = db_hash_map_tdb_error(dh);
}
return ret;
}
int db_hash_add(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
uint8_t *databuf, size_t datalen)
{
TDB_DATA key, data;
int ret;
if (dh == NULL) {
return EINVAL;
}
key.dptr = keybuf;
key.dsize = keylen;
data.dptr = databuf;
data.dsize = datalen;
ret = tdb_store(dh->db, key, data, TDB_REPLACE);
if (ret != 0) {
ret = db_hash_map_tdb_error(dh);
}
return ret;
}
int db_hash_delete(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen)
{
TDB_DATA key;
int ret;
key.dptr = keybuf;
key.dsize = keylen;
if (dh == NULL) {
return EINVAL;
}
ret = tdb_delete(dh->db, key);
if (ret != 0) {
ret = db_hash_map_tdb_error(dh);
}
return ret;
}
struct db_hash_fetch_state {
db_hash_record_parser_fn parser;
void *private_data;
};
static int db_hash_fetch_parser(TDB_DATA key, TDB_DATA data, void *private_data)
{
struct db_hash_fetch_state *state =
(struct db_hash_fetch_state *)private_data;
int ret;
ret = state->parser(key.dptr, key.dsize, data.dptr, data.dsize,
state->private_data);
return ret;
}
int db_hash_fetch(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
db_hash_record_parser_fn parser, void *private_data)
{
struct db_hash_fetch_state state;
TDB_DATA key;
int ret;
if (dh == NULL || parser == NULL) {
return EINVAL;
}
state.parser = parser;
state.private_data = private_data;
key.dptr = keybuf;
key.dsize = keylen;
ret = tdb_parse_record(dh->db, key, db_hash_fetch_parser, &state);
if (ret == -1) {
return ENOENT;
}
return ret;
}
int db_hash_exists(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen)
{
TDB_DATA key;
int ret;
if (dh == NULL) {
return EINVAL;
}
key.dptr = keybuf;
key.dsize = keylen;
ret = tdb_exists(dh->db, key);
if (ret == 1) {
/* Key found */
ret = 0;
} else {
ret = db_hash_map_tdb_error(dh);
if (ret == 0) {
ret = ENOENT;
}
}
return ret;
}
struct db_hash_traverse_state {
db_hash_record_parser_fn parser;
void *private_data;
};
static int db_hash_traverse_parser(struct tdb_context *tdb,
TDB_DATA key, TDB_DATA data,
void *private_data)
{
struct db_hash_traverse_state *state =
(struct db_hash_traverse_state *)private_data;
return state->parser(key.dptr, key.dsize, data.dptr, data.dsize,
state->private_data);
}
int db_hash_traverse(struct db_hash_context *dh,
db_hash_record_parser_fn parser, void *private_data,
int *count)
{
struct db_hash_traverse_state state;
int ret;
if (dh == NULL) {
return EINVAL;
}
/* Special case, for counting records */
if (parser == NULL) {
ret = tdb_traverse_read(dh->db, NULL, NULL);
} else {
state.parser = parser;
state.private_data = private_data;
ret = tdb_traverse_read(dh->db, db_hash_traverse_parser, &state);
}
if (ret == -1) {
ret = db_hash_map_tdb_error(dh);
} else {
if (count != NULL) {
*count = ret;
}
ret = 0;
}
return ret;
}

159
ctdb/common/db_hash.h Normal file
View File

@ -0,0 +1,159 @@
/*
Using tdb as a hash table
Copyright (C) Amitay Isaacs 2015
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CTDB_DB_HASH_H__
#define __CTDB_DB_HASH_H__
#include <talloc.h>
#include <tdb.h>
/**
* @file db_hash.h
*
* @brief Use tdb database as a hash table
*
* This uses in-memory tdb databases to create a fixed sized hash table.
*/
/**
* @brief Hash type to indicate the hashing function to use.
*
* DB_HASH_SIMPLE uses default hashing function
* DB_HASH_COMPLEX uses jenkins hashing function
*/
enum db_hash_type {
DB_HASH_SIMPLE,
DB_HASH_COMPLEX,
};
/**
* @brief Parser callback function called when fetching a record
*
* This function is called when fetching a record. This function should
* not modify key and data arguments.
*
* The function should return 0 on success and errno on error.
*/
typedef int (*db_hash_record_parser_fn)(uint8_t *keybuf, size_t keylen,
uint8_t *databuf, size_t datalen,
void *private_data);
/**
* @brief Abstract structure representing tdb hash table
*/
struct db_hash_context;
/**
* @brief Initialize tdb hash table
*
* This returns a new tdb hash table context which is a talloc context. Freeing
* this context will free all the memory associated with the hash table.
*
* @param[in] mem_ctx Talloc memory context
* @param[in] name The name for the hash table
* @param[in] hash_size The size of the hash table
* @param[in] type The type of hashing function to use
* @param[out] result The new db_hash_context structure
* @return 0 on success, errno on failure
*/
int db_hash_init(TALLOC_CTX *mem_ctx, const char *name, int hash_size,
enum db_hash_type type, struct db_hash_context **result);
/**
* @brief Insert a record into the hash table
*
* The key and data can be any binary data. Insert only if the record does not
* exist. If the record already exists, return error.
*
* @param[in] dh The tdb hash table context
* @param[in] keybuf The key buffer
* @param[in] keylen The key length
* @param[in] databuf The data buffer
* @param[in] datalen The data length
* @return 0 on success, errno on failure
*/
int db_hash_insert(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
uint8_t *databuf, size_t datalen);
/**
* @brief Add a record into the hash table
*
* The key and data can be any binary data. If the record does not exist,
* insert the record. If the record already exists, replace the record.
*
* @param[in] dh The tdb hash table context
* @param[in] keybuf The key buffer
* @param[in] keylen The key length
* @param[in] databuf The data buffer
* @param[in] datalen The data length
* @return 0 on success, errno on failure
*/
int db_hash_add(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
uint8_t *databuf, size_t datalen);
/**
* @brief Delete a record from the hash table
*
* @param[in] dh The tdb hash table context
* @param[in] keybuf The key buffer
* @param[in] keylen The key length
* @return 0 on success, errno on failure
*/
int db_hash_delete(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen);
/**
* @brief Fetch a record from the hash table
*
* The key and data can be any binary data.
*
* @param[in] dh The tdb hash table context
* @param[in] keybuf The key buffer
* @param[in] keylen The key length
* @param[in] parser Function called when the matching record is found
* @param[in] private_data Private data to parser function
* @return 0 on success, errno on failure
*/
int db_hash_fetch(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
db_hash_record_parser_fn parser, void *private_data);
/**
* @brief Check if a record exists in the hash table
*
* @param[in] dh The tdb hash table context
* @param[in] keybuf The key buffer
* @param[in] keylen The key length
* @return 0 if the record exists, errno on failure
*/
int db_hash_exists(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen);
/**
* @brief Traverse the database
*
* The parser function should non-zero value to stop traverse.
*
* @param[in] dh The tdb hash table context
* @param[in] parser Function called for each record
* @param[in] private_data Private data to parser function
* @param[out] count Number of records traversed
* @return 0 on success, errno on failure
*/
int db_hash_traverse(struct db_hash_context *dh,
db_hash_record_parser_fn parser, void *private_data,
int *count);
#endif /* __CTDB_DB_HASH_H__ */

View File

@ -0,0 +1,7 @@
#!/bin/sh
. "${TEST_SCRIPTS_DIR}/unit.sh"
ok_null
unit_test db_hash_test

View File

@ -255,7 +255,7 @@ export TEST_SCRIPTS_DIR="${test_dir}/scripts"
# If no tests specified then run some defaults
if [ -z "$1" ] ; then
if [ -n "$TEST_LOCAL_DAEMONS" ] ; then
set -- onnode takeover tool eventscripts simple
set -- onnode takeover tool eventscripts cunit simple
else
set -- simple complex
fi

View File

@ -0,0 +1,102 @@
/*
db_hash tests
Copyright (C) Amitay Isaacs 2015
This program 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 3 of the License, or
(at your option) any later version.
This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "replace.h"
#include <assert.h>
#include "common/db_hash.c"
static void do_test(enum db_hash_type type)
{
struct db_hash_context *dh;
TALLOC_CTX *mem_ctx = talloc_new(NULL);
TALLOC_CTX *tmp_ctx = talloc_new(NULL);
uint8_t *key = (uint8_t *)talloc_strdup(tmp_ctx, "This is a long key");
uint8_t *value = (uint8_t *)talloc_strdup(tmp_ctx, "This is a long value");
int ret;
ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
assert(ret == 0);
ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
assert(ret == 0);
ret = db_hash_exists(dh, key, sizeof(key));
assert(ret == 0);
ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
assert(ret == EEXIST);
ret = db_hash_delete(dh, key, sizeof(key));
assert(ret == 0);
ret = db_hash_exists(dh, key, sizeof(key));
assert(ret == ENOENT);
ret = db_hash_delete(dh, key, sizeof(key));
assert(ret == ENOENT);
ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key));
assert(ret == 0);
ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value));
assert(ret == 0);
talloc_free(dh);
ret = talloc_get_size(mem_ctx);
assert(ret == 0);
talloc_free(mem_ctx);
}
static void do_traverse_test(enum db_hash_type type)
{
struct db_hash_context *dh;
TALLOC_CTX *mem_ctx = talloc_new(NULL);
char key[] = "keyXXXX";
char value[] = "This is some test value";
int count, ret, i;
ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
assert(ret == 0);
for (i=0; i<2000; i++) {
sprintf(key, "key%04d", i);
ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key),
(uint8_t *)value, sizeof(value));
assert(ret == 0);
}
ret = db_hash_traverse(dh, NULL, NULL, &count);
assert(ret == 0);
assert(count == 2000);
talloc_free(dh);
talloc_free(mem_ctx);
}
int main(void)
{
do_test(DB_HASH_SIMPLE);
do_test(DB_HASH_COMPLEX);
do_traverse_test(DB_HASH_SIMPLE);
do_traverse_test(DB_HASH_COMPLEX);
return 0;
}

View File

@ -331,6 +331,11 @@ def build(bld):
includes='include include/internal',
deps='replace tevent tdb')
bld.SAMBA_SUBSYSTEM('ctdb-util',
source=bld.SUBDIR('common',
'''db_hash.c'''),
deps='replace talloc tevent tdb')
bld.SAMBA_SUBSYSTEM('ctdb-client',
source=bld.SUBDIR('client', 'ctdb_client.c'),
includes='include include/internal',
@ -565,6 +570,19 @@ def build(bld):
t.env.VERSION = VERSION
bld.INSTALL_FILES('${LIBDIR}/pkgconfig', 'ctdb.pc')
# Unit tests
ctdb_unit_tests = [
'db_hash_test',
]
for target in ctdb_unit_tests:
src = 'tests/src/' + target + '.c'
bld.SAMBA_BINARY(target,
source=src,
deps='talloc tevent tdb',
install_path='${CTDB_TEST_LIBDIR}')
# Test binaries
ctdb_tests = [
'rb_test',
@ -626,6 +644,7 @@ def build(bld):
test_subdirs = [
'complex',
'cunit',
'events.d',
'eventscripts',
'onnode',