1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "config.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 #include <memcached/engine.h>
8 #include "genhash.h"
9
10 #include "bucket_engine.h"
11
12 #define MAGIC 0xbeefcafe
13
14 #define ITEM_LINKED 1
15 #define ITEM_WITH_CAS 2
16
17 struct mock_stats {
18 int get_reqs;
19 int set_reqs;
20 int current;
21 };
22
23 struct mock_engine {
24 ENGINE_HANDLE_V1 engine;
25 uint64_t magic;
26 SERVER_HANDLE_V1 *server;
27 bool initialized;
28 genhash_t *hashtbl;
29 struct mock_stats stats;
30 int disconnects;
31 uint64_t magic2;
32
33 union {
34 engine_info engine_info;
35 char buffer[sizeof(engine_info) +
36 (sizeof(feature_info) * LAST_REGISTERED_ENGINE_FEATURE)];
37 } info;
38 };
39
40 typedef struct mock_item {
41 uint64_t cas;
42 rel_time_t exptime; /**< When the item will expire (relative to process
43 * startup) */
44 uint32_t nbytes; /**< The total size of the data (in bytes) */
45 uint32_t flags; /**< Flags associated with the item (in network byte order)*/
46 uint8_t datatype;
47 uint8_t clsid; /** class id for the object */
48 uint16_t nkey; /**< The total length of the key (in bytes) */
49 } mock_item;
50
51 MEMCACHED_PUBLIC_API
52 ENGINE_ERROR_CODE create_instance(uint64_t interface,
53 GET_SERVER_API gsapi,
54 ENGINE_HANDLE **handle);
55
56 static const engine_info* mock_get_info(ENGINE_HANDLE* handle);
57 static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
58 const char* config_str);
59 static void mock_destroy(ENGINE_HANDLE* handle,
60 const bool force);
61 static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
62 const void* cookie,
63 item **item,
64 const void* key,
65 const size_t nkey,
66 const size_t nbytes,
67 const int flags,
68 const rel_time_t exptime,
69 uint8_t datatype);
70 static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
71 const void* cookie,
72 const void* key,
73 const size_t nkey,
74 uint64_t* cas,
75 uint16_t vbucket);
76 static void mock_item_release(ENGINE_HANDLE* handle,
77 const void *cookie, item* item);
78 static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
79 const void* cookie,
80 item** item,
81 const void* key,
82 const int nkey,
83 uint16_t vbucket);
84 static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
85 const void *cookie,
86 const char *stat_key,
87 int nkey,
88 ADD_STAT add_stat);
89 static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie);
90 static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
91 const void *cookie,
92 item* item,
93 uint64_t *cas,
94 ENGINE_STORE_OPERATION operation,
95 uint16_t vbucket);
96 static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
97 const void* cookie,
98 const void* key,
99 const int nkey,
100 const bool increment,
101 const bool create,
102 const uint64_t delta,
103 const uint64_t initial,
104 const rel_time_t exptime,
105 uint64_t *cas,
106 uint8_t datatype,
107 uint64_t *result,
108 uint16_t vbucket);
109 static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
110 const void* cookie, time_t when);
111 static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
112 const void* cookie,
113 protocol_binary_request_header *request,
114 ADD_RESPONSE response);
115 static char* item_get_data(const item* item);
116 static const char* item_get_key(const item* item);
117 static void item_set_cas(ENGINE_HANDLE* handle, const void *cookie,
118 item* item, uint64_t val);
119 static uint64_t item_get_cas(const item* item);
120
121 static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
122 const item* item, item_info *item_info);
123
handle_disconnect(const void *cookie, ENGINE_EVENT_TYPE type, const void *event_data, const void *cb_data)124 static void handle_disconnect(const void *cookie,
125 ENGINE_EVENT_TYPE type,
126 const void *event_data,
127 const void *cb_data) {
128 struct mock_engine *h = (struct mock_engine*)cb_data;
129 (void)cookie;
130 (void)event_data;
131 cb_assert(type == ON_DISCONNECT);
132 ++h->disconnects;
133 }
134
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)135 static tap_event_t mock_tap_iterator(ENGINE_HANDLE* handle,
136 const void *cookie,
137 item **it,
138 void **engine_specific,
139 uint16_t *nengine_specific,
140 uint8_t *ttl,
141 uint16_t *flags,
142 uint32_t *seqno,
143 uint16_t *vbucket)
144 {
145 struct mock_engine *e = (struct mock_engine*)handle;
146 e->server->cookie->release(cookie);
147 (void)it; (void)engine_specific; (void)nengine_specific;
148 (void)ttl;(void)flags; (void)seqno; (void)vbucket;
149 return TAP_DISCONNECT;
150 }
151
152
153
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)154 static TAP_ITERATOR mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie,
155 const void* client, size_t nclient,
156 uint32_t flags,
157 const void* userdata, size_t nuserdata) {
158 struct mock_engine *e = (struct mock_engine*)handle;
159 (void)cookie;
160 (void)client;
161 (void)nclient;
162 (void)flags;
163 (void)userdata;
164 (void)nuserdata;
165 cb_assert(e->magic == MAGIC);
166 cb_assert(e->magic2 == MAGIC);
167
168 e->server->cookie->reserve(cookie);
169 return mock_tap_iterator;
170 }
171
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, uint8_t datatype, const void *data, size_t ndata, uint16_t vbucket)172 static ENGINE_ERROR_CODE mock_tap_notify(ENGINE_HANDLE* handle,
173 const void *cookie,
174 void *engine_specific,
175 uint16_t nengine,
176 uint8_t ttl,
177 uint16_t tap_flags,
178 tap_event_t tap_event,
179 uint32_t tap_seqno,
180 const void *key,
181 size_t nkey,
182 uint32_t flags,
183 uint32_t exptime,
184 uint64_t cas,
185 uint8_t datatype,
186 const void *data,
187 size_t ndata,
188 uint16_t vbucket) {
189 struct mock_engine *e = (struct mock_engine*)handle;
190 (void)e;
191 (void)cookie;
192 (void)engine_specific;
193 (void)nengine;
194 (void)ttl;
195 (void)tap_flags;
196 (void)tap_event;
197 (void)tap_seqno;
198 (void)key;
199 (void)nkey;
200 (void)flags;
201 (void)exptime;
202 (void)cas;
203 (void)datatype;
204 (void)data;
205 (void)ndata;
206 (void)vbucket;
207 cb_assert(e->magic == MAGIC);
208 cb_assert(e->magic2 == MAGIC);
209 return ENGINE_SUCCESS;
210 }
211
212 MEMCACHED_PUBLIC_API
create_instance(uint64_t interface, GET_SERVER_API gsapi, ENGINE_HANDLE **handle)213 ENGINE_ERROR_CODE create_instance(uint64_t interface,
214 GET_SERVER_API gsapi,
215 ENGINE_HANDLE **handle) {
216 struct mock_engine *h;
217 if (interface != 1) {
218 return ENGINE_ENOTSUP;
219 }
220
221 h = calloc(sizeof(struct mock_engine), 1);
222 cb_assert(h);
223 h->engine.interface.interface = 1;
224 h->engine.get_info = mock_get_info;
225 h->engine.initialize = mock_initialize;
226 h->engine.destroy = mock_destroy;
227 h->engine.allocate = mock_item_allocate;
228 h->engine.remove = mock_item_delete;
229 h->engine.release = mock_item_release;
230 h->engine.get = mock_get;
231 h->engine.get_stats = mock_get_stats;
232 h->engine.reset_stats = mock_reset_stats;
233 h->engine.store = mock_store;
234 h->engine.arithmetic = mock_arithmetic;
235 h->engine.flush = mock_flush;
236 h->engine.unknown_command = mock_unknown_command;
237 h->engine.item_set_cas = item_set_cas;
238 h->engine.get_item_info = get_item_info;
239 h->engine.get_tap_iterator = mock_get_tap_iterator;
240 h->engine.tap_notify = mock_tap_notify;
241
242 h->server = gsapi();
243
244 h->magic = MAGIC;
245 h->magic2 = MAGIC;
246
247 h->info.engine_info.description = "Mock engine v0.2";
248 h->info.engine_info.num_features = 0;
249
250 *handle = (ENGINE_HANDLE *)h;
251
252 return ENGINE_SUCCESS;
253 }
254
get_handle(ENGINE_HANDLE* handle)255 static struct mock_engine* get_handle(ENGINE_HANDLE* handle) {
256 struct mock_engine *e = (struct mock_engine*)handle;
257 cb_assert(e->magic == MAGIC);
258 cb_assert(e->magic2 == MAGIC);
259 return e;
260 }
261
mock_get_info(ENGINE_HANDLE* handle)262 static const engine_info* mock_get_info(ENGINE_HANDLE* handle) {
263 return &get_handle(handle)->info.engine_info;
264 }
265
my_hash_eq(const void *k1, size_t nkey1, const void *k2, size_t nkey2)266 static int my_hash_eq(const void *k1, size_t nkey1,
267 const void *k2, size_t nkey2) {
268 return nkey1 == nkey2 && memcmp(k1, k2, nkey1) == 0;
269 }
270
hash_strdup(const void *k, size_t nkey)271 static void* hash_strdup(const void *k, size_t nkey) {
272 void *rv = calloc(nkey, 1);
273 cb_assert(rv);
274 memcpy(rv, k, nkey);
275 return rv;
276 }
277
noop_dup(const void* ob, size_t vlen)278 static void* noop_dup(const void* ob, size_t vlen) {
279 (void)vlen;
280 return (void*)ob;
281 }
282
noop_free(void* ob)283 static void noop_free(void* ob) {
284 (void)ob;
285 /* Nothing */
286 }
287
288 static struct hash_ops my_hash_ops;
289
mock_initialize(ENGINE_HANDLE* handle, const char* config_str)290 static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
291 const char* config_str) {
292 struct mock_engine* se = get_handle(handle);
293 cb_assert(!se->initialized);
294
295 my_hash_ops.hashfunc = genhash_string_hash;
296 my_hash_ops.hasheq = my_hash_eq;
297 my_hash_ops.dupKey = hash_strdup;
298 my_hash_ops.dupValue = noop_dup;
299 my_hash_ops.freeKey = free;
300 my_hash_ops.freeValue = noop_free;
301
302 cb_assert(my_hash_ops.dupKey);
303
304 if (strcmp(config_str, "no_alloc") != 0) {
305 se->hashtbl = genhash_init(1, my_hash_ops);
306 cb_assert(se->hashtbl);
307 }
308
309 se->server->callback->register_callback((ENGINE_HANDLE*)se, ON_DISCONNECT,
310 handle_disconnect, se);
311
312 se->initialized = true;
313
314 return ENGINE_SUCCESS;
315 }
316
mock_destroy(ENGINE_HANDLE* handle, const bool force)317 static void mock_destroy(ENGINE_HANDLE* handle,
318 const bool force) {
319 struct mock_engine* se = get_handle(handle);
320 (void)force;
321
322 if (se->initialized) {
323 se->initialized = false;
324 genhash_free(se->hashtbl);
325 free(se);
326 }
327 }
328
get_ht(ENGINE_HANDLE *handle)329 static genhash_t *get_ht(ENGINE_HANDLE *handle) {
330 struct mock_engine* se = get_handle(handle);
331 cb_assert(se->initialized);
332 return se->hashtbl;
333 }
334
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, uint8_t datatype)335 static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
336 const void* cookie,
337 item **it,
338 const void* key,
339 const size_t nkey,
340 const size_t nbytes,
341 const int flags,
342 const rel_time_t exptime,
343 uint8_t datatype) {
344 (void)cookie;
345 /* Only perform allocations if there's a hashtable. */
346 if (get_ht(handle) != NULL) {
347 size_t to_alloc = sizeof(mock_item) + nkey + nbytes;
348 *it = calloc(to_alloc, 1);
349 } else {
350 *it = NULL;
351 }
352 /* If an allocation was requested *and* worked, fill and report success */
353 if (*it) {
354 mock_item* i = (mock_item*) *it;
355 i->datatype = datatype;
356 i->exptime = exptime;
357 i->nbytes = (uint32_t)nbytes;
358 i->flags = flags;
359 i->nkey = (uint16_t)nkey;
360 memcpy((char*)item_get_key(i), key, nkey);
361 return ENGINE_SUCCESS;
362 } else {
363 return ENGINE_ENOMEM;
364 }
365 }
366
mock_item_delete(ENGINE_HANDLE* handle, const void* cookie, const void* key, const size_t nkey, uint64_t* cas, uint16_t vbucket)367 static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
368 const void* cookie,
369 const void* key,
370 const size_t nkey,
371 uint64_t* cas,
372 uint16_t vbucket) {
373 int r = genhash_delete_all(get_ht(handle), key, nkey);
374 (void)cookie;
375 (void)cas;
376 (void)vbucket;
377 return r > 0 ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
378 }
379
mock_item_release(ENGINE_HANDLE* handle, const void *cookie, item* itm)380 static void mock_item_release(ENGINE_HANDLE* handle,
381 const void *cookie, item* itm) {
382 (void)handle;
383 (void)cookie;
384 free(itm);
385 }
386
mock_get(ENGINE_HANDLE* handle, const void* cookie, item** itm, const void* key, const int nkey, uint16_t vbucket)387 static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
388 const void* cookie,
389 item** itm,
390 const void* key,
391 const int nkey,
392 uint16_t vbucket) {
393 (void)cookie;
394 (void)vbucket;
395 *itm = genhash_find(get_ht(handle), key, nkey);
396
397 return *itm ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
398 }
399
mock_get_stats(ENGINE_HANDLE* handle, const void* cookie, const char* stat_key, int nkey, ADD_STAT add_stat)400 static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
401 const void* cookie,
402 const char* stat_key,
403 int nkey,
404 ADD_STAT add_stat)
405 {
406 (void)handle;
407 (void)cookie;
408 (void)stat_key;
409 (void)nkey;
410 (void)add_stat;
411 /* TODO: Implement */
412 return ENGINE_SUCCESS;
413 }
414
mock_store(ENGINE_HANDLE* handle, const void *cookie, item* itm, uint64_t *cas, ENGINE_STORE_OPERATION operation, uint16_t vbucket)415 static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
416 const void *cookie,
417 item* itm,
418 uint64_t *cas,
419 ENGINE_STORE_OPERATION operation,
420 uint16_t vbucket) {
421 mock_item* it = (mock_item*)itm;
422 (void)cookie;
423 (void)cas;
424 (void)vbucket;
425 (void)operation;
426 genhash_update(get_ht(handle), item_get_key(itm), it->nkey, itm, 0);
427 return ENGINE_SUCCESS;
428 }
429
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, uint8_t datatype, uint64_t *result, uint16_t vbucket)430 static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
431 const void* cookie,
432 const void* key,
433 const int nkey,
434 const bool increment,
435 const bool create,
436 const uint64_t delta,
437 const uint64_t initial,
438 const rel_time_t exptime,
439 uint64_t *cas,
440 uint8_t datatype,
441 uint64_t *result,
442 uint16_t vbucket) {
443 item *item_in = NULL, *item_out = NULL;
444 int flags = 0;
445 char buf[32];
446 ENGINE_ERROR_CODE rv;
447 (void)increment;
448 (void)vbucket;
449 *cas = 0;
450
451 if (mock_get(handle, cookie, &item_in, key, nkey, 0) == ENGINE_SUCCESS) {
452 /* Found, just do the math. */
453 /* This is all int stuff, just to make it easy. */
454 *result = atoi(item_get_data(item_in));
455 *result += delta;
456 flags = ((mock_item*) item_in)->flags;
457 } else if (create) {
458 /* Not found, do the initialization */
459 *result = initial;
460 } else {
461 /* Reject. */
462 return ENGINE_KEY_ENOENT;
463 }
464
465 snprintf(buf, sizeof(buf), "%"PRIu64, *result);
466 if((rv = mock_item_allocate(handle, cookie, &item_out,
467 key, nkey,
468 strlen(buf) + 1,
469 flags, exptime,
470 datatype)) != ENGINE_SUCCESS) {
471 return rv;
472 }
473 memcpy(item_get_data(item_out), buf, strlen(buf) + 1);
474 mock_store(handle, cookie, item_out, 0, OPERATION_SET, 0);
475 return ENGINE_SUCCESS;
476 }
477
mock_flush(ENGINE_HANDLE* handle, const void* cookie, time_t when)478 static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
479 const void* cookie, time_t when) {
480 (void)cookie;
481 (void)when;
482 genhash_clear(get_ht(handle));
483 return ENGINE_SUCCESS;
484 }
485
mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie)486 static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {
487 (void)handle;
488 (void)cookie;
489 /* TODO: Implement */
490 }
491
mock_unknown_command(ENGINE_HANDLE* handle, const void* cookie, protocol_binary_request_header *request, ADD_RESPONSE response)492 static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
493 const void* cookie,
494 protocol_binary_request_header *request,
495 ADD_RESPONSE response)
496 {
497 switch (request->request.opcode) {
498 case PROTOCOL_BINARY_CMD_GET_REPLICA:
499 case PROTOCOL_BINARY_CMD_EVICT_KEY:
500 case PROTOCOL_BINARY_CMD_GET_LOCKED:
501 case PROTOCOL_BINARY_CMD_UNLOCK_KEY:
502 case PROTOCOL_BINARY_CMD_GET_META:
503 case PROTOCOL_BINARY_CMD_GETQ_META:
504 case PROTOCOL_BINARY_CMD_SET_WITH_META:
505 case PROTOCOL_BINARY_CMD_SETQ_WITH_META:
506 case PROTOCOL_BINARY_CMD_DEL_WITH_META:
507 case PROTOCOL_BINARY_CMD_DELQ_WITH_META:
508 return ENGINE_SUCCESS;
509 }
510
511 (void)handle; (void)cookie; (void)response;
512 return ENGINE_ENOTSUP;
513 }
514
item_get_cas(const item* itm)515 static uint64_t item_get_cas(const item* itm)
516 {
517 const mock_item* it = (mock_item*)itm;
518 return it->cas;
519 }
520
item_set_cas(ENGINE_HANDLE *handle, const void *cookie, item* itm, uint64_t val)521 static void item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
522 item* itm, uint64_t val)
523 {
524 mock_item* it = (mock_item*)itm;
525 (void)handle;
526 (void)cookie;
527 it->cas = val;
528 }
529
item_get_key(const item* itm)530 static const char* item_get_key(const item* itm)
531 {
532 const mock_item* it = (mock_item*)itm;
533 char *ret = (void*)(it + 1);
534 return ret;
535 }
536
item_get_data(const item* itm)537 static char* item_get_data(const item* itm)
538 {
539 const mock_item* it = (mock_item*)itm;
540 return ((char*)item_get_key(itm)) + it->nkey;
541 }
542
get_item_info(ENGINE_HANDLE *handle, const void *cookie, const item* itm, item_info *itm_info)543 static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
544 const item* itm, item_info *itm_info)
545 {
546 mock_item* it = (mock_item*)itm;
547 (void)handle;
548 (void)cookie;
549 if (itm_info->nvalue < 1) {
550 return false;
551 }
552 itm_info->cas = item_get_cas(it);
553 itm_info->exptime = it->exptime;
554 itm_info->nbytes = it->nbytes;
555 itm_info->flags = it->flags;
556 itm_info->clsid = it->clsid;
557 itm_info->nkey = it->nkey;
558 itm_info->nvalue = 1;
559 itm_info->key = item_get_key(it);
560 itm_info->value[0].iov_base = item_get_data(it);
561 itm_info->value[0].iov_len = it->nbytes;
562 return true;
563 }
564