Permalink
Fetching contributors…
Cannot retrieve contributors at this time
554 lines (493 sloc) 18.8 KB
/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <memcached/engine.h>
#include <ep-engine/command_ids.h>
#include "genhash.h"
#include "bucket_engine.h"
#define MAGIC 0x426D4639C1BFEC3ll
#define ITEM_LINKED 1
#define ITEM_WITH_CAS 2
struct mock_stats {
int get_reqs;
int set_reqs;
int current;
};
struct mock_engine {
ENGINE_HANDLE_V1 engine;
uint64_t magic;
SERVER_HANDLE_V1 *server;
bool initialized;
genhash_t *hashtbl;
struct mock_stats stats;
int disconnects;
uint64_t magic2;
union {
engine_info engine_info;
char buffer[sizeof(engine_info) +
(sizeof(feature_info) * LAST_REGISTERED_ENGINE_FEATURE)];
} info;
};
typedef struct mock_item {
uint64_t cas;
rel_time_t exptime; /**< When the item will expire (relative to process
* startup) */
uint32_t nbytes; /**< The total size of the data (in bytes) */
uint32_t flags; /**< Flags associated with the item (in network byte order)*/
uint8_t clsid; /** class id for the object */
uint16_t nkey; /**< The total length of the key (in bytes) */
} mock_item;
MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface,
GET_SERVER_API gsapi,
ENGINE_HANDLE **handle);
static const engine_info* mock_get_info(ENGINE_HANDLE* handle);
static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
const char* config_str);
static void mock_destroy(ENGINE_HANDLE* handle,
const bool force);
static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
const void* cookie,
item **item,
const void* key,
const size_t nkey,
const size_t nbytes,
const int flags,
const rel_time_t exptime);
static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
const void* cookie,
const void* key,
const size_t nkey,
uint64_t* cas,
uint16_t vbucket);
static void mock_item_release(ENGINE_HANDLE* handle,
const void *cookie, item* item);
static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
const void* cookie,
item** item,
const void* key,
const int nkey,
uint16_t vbucket);
static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
const void *cookie,
const char *stat_key,
int nkey,
ADD_STAT add_stat);
static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie);
static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
const void *cookie,
item* item,
uint64_t *cas,
ENGINE_STORE_OPERATION operation,
uint16_t vbucket);
static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
const void* cookie,
const void* key,
const int nkey,
const bool increment,
const bool create,
const uint64_t delta,
const uint64_t initial,
const rel_time_t exptime,
uint64_t *cas,
uint64_t *result,
uint16_t vbucket);
static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
const void* cookie, time_t when);
static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
const void* cookie,
protocol_binary_request_header *request,
ADD_RESPONSE response);
static char* item_get_data(const item* item);
static const char* item_get_key(const item* item);
static void item_set_cas(ENGINE_HANDLE* handle, const void *cookie,
item* item, uint64_t val);
static uint64_t item_get_cas(const item* item);
static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
const item* item, item_info *item_info);
static void handle_disconnect(const void *cookie,
ENGINE_EVENT_TYPE type,
const void *event_data,
const void *cb_data) {
(void)cookie;
(void)event_data;
assert(type == ON_DISCONNECT);
struct mock_engine *h = (struct mock_engine*)cb_data;
++h->disconnects;
}
static tap_event_t mock_tap_iterator(ENGINE_HANDLE* handle,
const void *cookie,
item **it,
void **engine_specific,
uint16_t *nengine_specific,
uint8_t *ttl,
uint16_t *flags,
uint32_t *seqno,
uint16_t *vbucket)
{
struct mock_engine *e = (struct mock_engine*)handle;
e->server->cookie->release(cookie);
(void)it; (void)engine_specific; (void)nengine_specific;
(void)ttl;(void)flags; (void)seqno; (void)vbucket;
return TAP_DISCONNECT;
}
static TAP_ITERATOR mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie,
const void* client, size_t nclient,
uint32_t flags,
const void* userdata, size_t nuserdata) {
(void)cookie;
(void)client;
(void)nclient;
(void)flags;
(void)userdata;
(void)nuserdata;
struct mock_engine *e = (struct mock_engine*)handle;
assert(e->magic == MAGIC);
assert(e->magic2 == MAGIC);
e->server->cookie->reserve(cookie);
return mock_tap_iterator;
}
static ENGINE_ERROR_CODE mock_tap_notify(ENGINE_HANDLE* handle,
const void *cookie,
void *engine_specific,
uint16_t nengine,
uint8_t ttl,
uint16_t tap_flags,
tap_event_t tap_event,
uint32_t tap_seqno,
const void *key,
size_t nkey,
uint32_t flags,
uint32_t exptime,
uint64_t cas,
const void *data,
size_t ndata,
uint16_t vbucket) {
struct mock_engine *e = (struct mock_engine*)handle;
(void)cookie;
(void)engine_specific;
(void)nengine;
(void)ttl;
(void)tap_flags;
(void)tap_event;
(void)tap_seqno;
(void)key;
(void)nkey;
(void)flags;
(void)exptime;
(void)cas;
(void)data;
(void)ndata;
(void)vbucket;
assert(e->magic == MAGIC);
assert(e->magic2 == MAGIC);
return ENGINE_SUCCESS;
}
MEMCACHED_PUBLIC_API
ENGINE_ERROR_CODE create_instance(uint64_t interface,
GET_SERVER_API gsapi,
ENGINE_HANDLE **handle) {
if (interface != 1) {
return ENGINE_ENOTSUP;
}
struct mock_engine *h = calloc(sizeof(struct mock_engine), 1);
assert(h);
h->engine.interface.interface = 1;
h->engine.get_info = mock_get_info;
h->engine.initialize = mock_initialize;
h->engine.destroy = mock_destroy;
h->engine.allocate = mock_item_allocate;
h->engine.remove = mock_item_delete;
h->engine.release = mock_item_release;
h->engine.get = mock_get;
h->engine.get_stats = mock_get_stats;
h->engine.reset_stats = mock_reset_stats;
h->engine.store = mock_store;
h->engine.arithmetic = mock_arithmetic;
h->engine.flush = mock_flush;
h->engine.unknown_command = mock_unknown_command;
h->engine.item_set_cas = item_set_cas;
h->engine.get_item_info = get_item_info;
h->engine.get_tap_iterator = mock_get_tap_iterator;
h->engine.tap_notify = mock_tap_notify;
h->server = gsapi();
h->magic = MAGIC;
h->magic2 = MAGIC;
h->info.engine_info.description = "Mock engine v0.2";
h->info.engine_info.num_features = 0;
*handle = (ENGINE_HANDLE *)h;
return ENGINE_SUCCESS;
}
static inline struct mock_engine* get_handle(ENGINE_HANDLE* handle) {
struct mock_engine *e = (struct mock_engine*)handle;
assert(e->magic == MAGIC);
assert(e->magic2 == MAGIC);
return e;
}
static const engine_info* mock_get_info(ENGINE_HANDLE* handle) {
return &get_handle(handle)->info.engine_info;
}
static int my_hash_eq(const void *k1, size_t nkey1,
const void *k2, size_t nkey2) {
return nkey1 == nkey2 && memcmp(k1, k2, nkey1) == 0;
}
static void* hash_strdup(const void *k, size_t nkey) {
void *rv = calloc(nkey, 1);
assert(rv);
memcpy(rv, k, nkey);
return rv;
}
static void* noop_dup(const void* ob, size_t vlen) {
(void)vlen;
return (void*)ob;
}
static void noop_free(void* ob) {
(void)ob;
// Nothing
}
static struct hash_ops my_hash_ops = {
.hashfunc = genhash_string_hash,
.hasheq = my_hash_eq,
.dupKey = hash_strdup,
.dupValue = noop_dup,
.freeKey = free,
.freeValue = noop_free
};
static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
const char* config_str) {
struct mock_engine* se = get_handle(handle);
assert(!se->initialized);
assert(my_hash_ops.dupKey);
if (strcmp(config_str, "no_alloc") != 0) {
se->hashtbl = genhash_init(1, my_hash_ops);
assert(se->hashtbl);
}
se->server->callback->register_callback((ENGINE_HANDLE*)se, ON_DISCONNECT,
handle_disconnect, se);
se->initialized = true;
return ENGINE_SUCCESS;
}
static void mock_destroy(ENGINE_HANDLE* handle,
const bool force) {
(void)force;
struct mock_engine* se = get_handle(handle);
if (se->initialized) {
se->initialized = false;
genhash_free(se->hashtbl);
free(se);
}
}
static genhash_t *get_ht(ENGINE_HANDLE *handle) {
struct mock_engine* se = get_handle(handle);
assert(se->initialized);
return se->hashtbl;
}
static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
const void* cookie,
item **it,
const void* key,
const size_t nkey,
const size_t nbytes,
const int flags,
const rel_time_t exptime) {
(void)cookie;
// Only perform allocations if there's a hashtable.
if (get_ht(handle) != NULL) {
size_t to_alloc = sizeof(mock_item) + nkey + nbytes;
*it = calloc(to_alloc, 1);
} else {
*it = NULL;
}
// If an allocation was requested *and* worked, fill and report success
if (*it) {
mock_item* i = (mock_item*) *it;
i->exptime = exptime;
i->nbytes = nbytes;
i->flags = flags;
i->nkey = nkey;
memcpy((char*)item_get_key(i), key, nkey);
return ENGINE_SUCCESS;
} else {
return ENGINE_ENOMEM;
}
}
static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
const void* cookie,
const void* key,
const size_t nkey,
uint64_t* cas,
uint16_t vbucket) {
(void)cookie;
(void)cas;
(void)vbucket;
int r = genhash_delete_all(get_ht(handle), key, nkey);
return r > 0 ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
}
static void mock_item_release(ENGINE_HANDLE* handle,
const void *cookie, item* itm) {
(void)handle;
(void)cookie;
free(itm);
}
static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
const void* cookie,
item** itm,
const void* key,
const int nkey,
uint16_t vbucket) {
(void)cookie;
(void)vbucket;
*itm = genhash_find(get_ht(handle), key, nkey);
return *itm ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
}
static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
const void* cookie,
const char* stat_key,
int nkey,
ADD_STAT add_stat)
{
(void)handle;
(void)cookie;
(void)stat_key;
(void)nkey;
(void)add_stat;
// TODO: Implement
return ENGINE_SUCCESS;
}
static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
const void *cookie,
item* itm,
uint64_t *cas,
ENGINE_STORE_OPERATION operation,
uint16_t vbucket) {
(void)cookie;
(void)cas;
(void)vbucket;
(void)operation;
mock_item* it = (mock_item*)itm;
genhash_update(get_ht(handle), item_get_key(itm), it->nkey, itm, 0);
return ENGINE_SUCCESS;
}
static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
const void* cookie,
const void* key,
const int nkey,
const bool increment,
const bool create,
const uint64_t delta,
const uint64_t initial,
const rel_time_t exptime,
uint64_t *cas,
uint64_t *result,
uint16_t vbucket) {
(void)increment;
(void)vbucket;
item *item_in = NULL, *item_out = NULL;
int flags = 0;
*cas = 0;
if (mock_get(handle, cookie, &item_in, key, nkey, 0) == ENGINE_SUCCESS) {
// Found, just do the math.
// This is all int stuff, just to make it easy.
*result = atoi(item_get_data(item_in));
*result += delta;
flags = ((mock_item*) item_in)->flags;
} else if (create) {
// Not found, do the initialization
*result = initial;
} else {
// Reject.
return ENGINE_KEY_ENOENT;
}
char buf[32];
snprintf(buf, sizeof(buf), "%"PRIu64, *result);
ENGINE_ERROR_CODE rv;
if((rv = mock_item_allocate(handle, cookie, &item_out,
key, nkey,
strlen(buf) + 1,
flags, exptime)) != ENGINE_SUCCESS) {
return rv;
}
memcpy(item_get_data(item_out), buf, strlen(buf) + 1);
mock_store(handle, cookie, item_out, 0, OPERATION_SET, 0);
return ENGINE_SUCCESS;
}
static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
const void* cookie, time_t when) {
(void)cookie;
(void)when;
genhash_clear(get_ht(handle));
return ENGINE_SUCCESS;
}
static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {
(void)handle;
(void)cookie;
// TODO: Implement
}
static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
const void* cookie,
protocol_binary_request_header *request,
ADD_RESPONSE response)
{
switch (request->request.opcode) {
case CMD_GET_REPLICA:
case CMD_EVICT_KEY:
case CMD_GET_LOCKED:
case CMD_UNLOCK_KEY:
case CMD_GET_META:
case CMD_GETQ_META:
case CMD_SET_WITH_META:
case CMD_SETQ_WITH_META:
case CMD_DEL_WITH_META:
case CMD_DELQ_WITH_META:
return ENGINE_SUCCESS;
}
(void)handle; (void)cookie; (void)response;
return ENGINE_ENOTSUP;
}
static uint64_t item_get_cas(const item* itm)
{
const mock_item* it = (mock_item*)itm;
return it->cas;
}
static void item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
item* itm, uint64_t val)
{
(void)handle;
(void)cookie;
mock_item* it = (mock_item*)itm;
it->cas = val;
}
static const char* item_get_key(const item* itm)
{
const mock_item* it = (mock_item*)itm;
char *ret = (void*)(it + 1);
return ret;
}
static char* item_get_data(const item* itm)
{
const mock_item* it = (mock_item*)itm;
return ((char*)item_get_key(itm)) + it->nkey;
}
static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
const item* itm, item_info *itm_info)
{
(void)handle;
(void)cookie;
mock_item* it = (mock_item*)itm;
if (itm_info->nvalue < 1) {
return false;
}
itm_info->cas = item_get_cas(it);
itm_info->exptime = it->exptime;
itm_info->nbytes = it->nbytes;
itm_info->flags = it->flags;
itm_info->clsid = it->clsid;
itm_info->nkey = it->nkey;
itm_info->nvalue = 1;
itm_info->key = item_get_key(it);
itm_info->value[0].iov_base = item_get_data(it);
itm_info->value[0].iov_len = it->nbytes;
return true;
}