Merged Stephen Yang's and David Terei's binary protocol and SASL implementations.

I ended up cherry-picking different parts from each implementation.
I liked David's changes to the read state machine to implement SASL.
I used Stephene's implementation of the binary protocol integrated with
the ASCII protocol.
This commit is contained in:
Jacob Leverich 2013-03-04 09:31:13 -08:00
parent ff6987e8d7
commit 4e8d2f6d10
6 changed files with 75 additions and 95 deletions

View File

@ -18,10 +18,9 @@
Connection::Connection(struct event_base* _base, struct evdns_base* _evdns, Connection::Connection(struct event_base* _base, struct evdns_base* _evdns,
string _hostname, string _port, options_t _options, string _hostname, string _port, options_t _options,
bool useBinary,bool sampling) : bool sampling) :
hostname(_hostname), port(_port), start_time(0), hostname(_hostname), port(_port), start_time(0),
stats(sampling), options(_options), base(_base), evdns(_evdns), stats(sampling), options(_options), base(_base), evdns(_evdns)
useBinary(useBinary)
{ {
valuesize = createGenerator(options.valuesize); valuesize = createGenerator(options.valuesize);
keysize = createGenerator(options.keysize); keysize = createGenerator(options.keysize);
@ -74,30 +73,26 @@ void Connection::reset() {
} }
void Connection::doPlaintextSASL(string username, string password) { void Connection::issue_sasl() {
// each line is 4 bytes
binary_header h = { 0x80, CMD_SASL, htons(0x05),
0x00, 0x00, 0x00,
htonl(5 + 1 + username.length() + 1 + password.length())};
Operation op;
// This is only done once, so efficiency is not as important.
bufferevent_write(bev, &h, 24);
bufferevent_write(bev, "PLAIN", 5);
bufferevent_write(bev, "\0" , 1);
bufferevent_write(bev, username.c_str(), username.length());
bufferevent_write(bev, "\0" , 1);
bufferevent_write(bev, password.c_str(), password.length());
// Don't care about key nor time
op.type = Operation::SASL;
op_queue.push(op);
read_state = WAITING_FOR_SASL; read_state = WAITING_FOR_SASL;
string username = string(options.username);
string password = string(options.password);
binary_header_t header = {0x80, CMD_SASL, 0, 0, 0, 0, 0, 0, 0};
header.key_len = htons(5);
header.body_len = htonl(6 + username.length() + 1 + password.length());
bufferevent_write(bev, &header, 24);
bufferevent_write(bev, "PLAIN\0", 6);
bufferevent_write(bev, username.c_str(), username.length() + 1);
bufferevent_write(bev, password.c_str(), password.length());
} }
void Connection::issue_get(const char* key, uint16_t keylen, double now) { void Connection::issue_get(const char* key, double now) {
Operation op; Operation op;
int l; int l;
uint16_t keylen = strlen(key);
#if HAVE_CLOCK_GETTIME #if HAVE_CLOCK_GETTIME
op.start_time = get_time_accurate(); op.start_time = get_time_accurate();
@ -124,26 +119,28 @@ void Connection::issue_get(const char* key, uint16_t keylen, double now) {
if (read_state == IDLE) if (read_state == IDLE)
read_state = WAITING_FOR_GET; read_state = WAITING_FOR_GET;
if (useBinary) { if (options.binary) {
// each line is 4-bytes // each line is 4-bytes
binary_header h = {0x80, CMD_GET, htons(keylen), binary_header_t h = {0x80, CMD_GET, htons(keylen),
0x00, 0x00, htons(0), //TODO(syang0) get actual vbucket? 0x00, 0x00, htons(0), //TODO(syang0) get actual vbucket?
htonl(keylen) }; htonl(keylen) };
l = bufferevent_write(bev, &h, 24); // size does not include extras bufferevent_write(bev, &h, 24); // size does not include extras
l += bufferevent_write(bev, key, keylen); bufferevent_write(bev, key, keylen);
l = 24 + keylen;
} else { } else {
l = evbuffer_add_printf(bufferevent_get_output(bev), "get %s\r\n", key); l = evbuffer_add_printf(bufferevent_get_output(bev), "get %s\r\n", key);
} }
if (read_state != LOADING) stats.tx_bytes += l; if (read_state != LOADING) stats.tx_bytes += l;
} }
void Connection::issue_set(const char* key, uint16_t keylen, void Connection::issue_set(const char* key,
const char* value, int length, const char* value, int length,
double now) { double now) {
Operation op; Operation op;
int l; int l;
uint16_t keylen = strlen(key);
#if HAVE_CLOCK_GETTIME #if HAVE_CLOCK_GETTIME
op.start_time = get_time_accurate(); op.start_time = get_time_accurate();
@ -158,9 +155,9 @@ void Connection::issue_set(const char* key, uint16_t keylen,
if (read_state == IDLE) if (read_state == IDLE)
read_state = WAITING_FOR_SET; read_state = WAITING_FOR_SET;
if (useBinary) { if (options.binary) {
// each line is 4-bytes // each line is 4-bytes
binary_header h = { 0x80, CMD_SET, htons(keylen), binary_header_t h = { 0x80, CMD_SET, htons(keylen),
0x08, 0x00, htons(0), //TODO(syang0) get actual vbucket? 0x08, 0x00, htons(0), //TODO(syang0) get actual vbucket?
htonl(keylen + 8 + length)}; htonl(keylen + 8 + length)};
@ -171,8 +168,9 @@ void Connection::issue_set(const char* key, uint16_t keylen,
} else { } else {
l = evbuffer_add_printf(bufferevent_get_output(bev), l = evbuffer_add_printf(bufferevent_get_output(bev),
"set %s 0 0 %d\r\n", key, length); "set %s 0 0 %d\r\n", key, length);
l += bufferevent_write(bev, value, length); bufferevent_write(bev, value, length);
l += bufferevent_write(bev, "\r\n", 2); bufferevent_write(bev, "\r\n", 2);
l += length + 2;
} }
if (read_state != LOADING) stats.tx_bytes += l; if (read_state != LOADING) stats.tx_bytes += l;
@ -183,16 +181,15 @@ void Connection::issue_something(double now) {
// FIXME: generate key distribution here! // FIXME: generate key distribution here!
string keystr = keygen->generate(lrand48() % options.records); string keystr = keygen->generate(lrand48() % options.records);
strcpy(key, keystr.c_str()); strcpy(key, keystr.c_str());
uint8_t keylen = keystr.length();
// int key_index = lrand48() % options.records; // int key_index = lrand48() % options.records;
// generate_key(key_index, options.keysize, key); // generate_key(key_index, options.keysize, key);
if (drand48() < options.update) { if (drand48() < options.update) {
int index = lrand48() % (1024 * 1024); int index = lrand48() % (1024 * 1024);
// issue_set(key, &random_char[index], options.valuesize, now); // issue_set(key, &random_char[index], options.valuesize, now);
issue_set(key, keylen, &random_char[index], valuesize->generate(), now); issue_set(key, &random_char[index], valuesize->generate(), now);
} else { } else {
issue_get(key, keylen, now); issue_get(key, now);
} }
} }
@ -315,7 +312,10 @@ void Connection::event_callback(short events) {
DIE("setsockopt()"); DIE("setsockopt()");
} }
read_state = IDLE; // This is the most important part! if (options.sasl)
issue_sasl();
else
read_state = IDLE; // This is the most important part!
} else if (events & BEV_EVENT_ERROR) { } else if (events & BEV_EVENT_ERROR) {
int err = bufferevent_socket_get_dns_error(bev); int err = bufferevent_socket_get_dns_error(bev);
if (err) DIE("DNS error: %s", evutil_gai_strerror(err)); if (err) DIE("DNS error: %s", evutil_gai_strerror(err));
@ -356,7 +356,7 @@ void Connection::read_callback() {
case WAITING_FOR_GET: case WAITING_FOR_GET:
assert(op_queue.size() > 0); assert(op_queue.size() > 0);
if (useBinary) { if (options.binary) {
if (consume_binary_response(input)) { if (consume_binary_response(input)) {
#if USE_CACHED_TIME #if USE_CACHED_TIME
now = tv_to_double(&now_tv); now = tv_to_double(&now_tv);
@ -466,7 +466,7 @@ void Connection::read_callback() {
case WAITING_FOR_SET: case WAITING_FOR_SET:
assert(op_queue.size() > 0); assert(op_queue.size() > 0);
if (useBinary) { if (options.binary) {
if (!consume_binary_response(input)) return; if (!consume_binary_response(input)) return;
} else { } else {
buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF); buf = evbuffer_readln(input, &n_read_out, EVBUFFER_EOL_CRLF);
@ -484,7 +484,7 @@ void Connection::read_callback() {
stats.log_set(*op); stats.log_set(*op);
if (!useBinary) if (!options.binary)
free(buf); free(buf);
pop_op(); pop_op();
@ -494,7 +494,7 @@ void Connection::read_callback() {
case LOADING: case LOADING:
assert(op_queue.size() > 0); assert(op_queue.size() > 0);
if (useBinary) { if (options.binary) {
if (!consume_binary_response(input)) return; if (!consume_binary_response(input)) return;
} else { } else {
buf = evbuffer_readln(input, NULL, EVBUFFER_EOL_CRLF); buf = evbuffer_readln(input, NULL, EVBUFFER_EOL_CRLF);
@ -518,8 +518,7 @@ void Connection::read_callback() {
int index = lrand48() % (1024 * 1024); int index = lrand48() % (1024 * 1024);
// generate_key(loader_issued, options.keysize, key); // generate_key(loader_issued, options.keysize, key);
// issue_set(key, &random_char[index], options.valuesize); // issue_set(key, &random_char[index], options.valuesize);
issue_set(key, keystr.length(), &random_char[index], issue_set(key, &random_char[index], valuesize->generate());
valuesize->generate());
loader_issued++; loader_issued++;
} }
@ -528,9 +527,9 @@ void Connection::read_callback() {
break; break;
case WAITING_FOR_SASL: case WAITING_FOR_SASL:
assert(op_queue.size() > 0); assert(options.binary);
if (!consume_binary_response(input)) return; if (!consume_binary_response(input)) return;
pop_op(); read_state = IDLE;
break; break;
default: DIE("not implemented"); default: DIE("not implemented");
@ -548,8 +547,8 @@ bool Connection::consume_binary_response(evbuffer *input) {
// Read the first 24 bytes as a header // Read the first 24 bytes as a header
int length = evbuffer_get_length(input); int length = evbuffer_get_length(input);
if (length < 24) return false; if (length < 24) return false;
binary_header* h = binary_header_t* h =
reinterpret_cast<binary_header*>(evbuffer_pullup(input, 24)); reinterpret_cast<binary_header_t*>(evbuffer_pullup(input, 24));
assert(h); assert(h);
// Not whole response // Not whole response
@ -619,7 +618,7 @@ void Connection::start_loading() {
strcpy(key, keystr.c_str()); strcpy(key, keystr.c_str());
// generate_key(loader_issued, options.keysize, key); // generate_key(loader_issued, options.keysize, key);
// issue_set(key, &random_char[index], options.valuesize); // issue_set(key, &random_char[index], options.valuesize);
issue_set(key, keystr.length(), &random_char[index], valuesize->generate()); issue_set(key, &random_char[index], valuesize->generate());
loader_issued++; loader_issued++;
} }
} }

View File

@ -27,7 +27,7 @@ class Connection {
public: public:
Connection(struct event_base* _base, struct evdns_base* _evdns, Connection(struct event_base* _base, struct evdns_base* _evdns,
string _hostname, string _port, options_t options, string _hostname, string _port, options_t options,
bool useBinary, bool sampling = true); bool sampling = true);
~Connection(); ~Connection();
string hostname; string hostname;
@ -60,8 +60,8 @@ public:
ConnectionStats stats; ConnectionStats stats;
void issue_get(const char* key, uint16_t keylen, double now = 0.0); void issue_get(const char* key, double now = 0.0);
void issue_set(const char* key, uint16_t keylen, const char* value, int length, void issue_set(const char* key, const char* value, int length,
double now = 0.0); double now = 0.0);
void issue_something(double now = 0.0); void issue_something(double now = 0.0);
void pop_op(); void pop_op();
@ -71,7 +71,7 @@ public:
void start_loading(); void start_loading();
void reset(); void reset();
void doPlaintextSASL(string username, string password); void issue_sasl();
void event_callback(short events); void event_callback(short events);
void read_callback(); void read_callback();

View File

@ -10,6 +10,11 @@ typedef struct {
int qps; int qps;
int records; int records;
bool binary;
bool sasl;
char username[32];
char password[32];
char keysize[32]; char keysize[32];
char valuesize[32]; char valuesize[32];
// int keysize; // int keysize;

View File

@ -1,10 +1,3 @@
/*
* File: binary_protocol.h
* Author: syang0
*
* Created on March 1, 2013, 12:59 AM
*/
#ifndef BINARY_PROTOCOL_H #ifndef BINARY_PROTOCOL_H
#define BINARY_PROTOCOL_H #define BINARY_PROTOCOL_H
@ -33,7 +26,6 @@ typedef struct __attribute__ ((__packed__)) {
// Used for set only. // Used for set only.
uint64_t extras; uint64_t extras;
} binary_header; } binary_header_t;
#endif /* BINARY_PROTOCOL_H */
#endif /* BINARY_PROTOCOL_H */

View File

@ -29,12 +29,14 @@ option "update" u "Ratio of set:get commands." float default="0.0"
text "\nAdvanced options:" text "\nAdvanced options:"
option "username" U "Username to use for SASL authentication." string
option "password" P "Password to use for SASL authentication." string
option "threads" T "Number of threads to spawn." int default="1" option "threads" T "Number of threads to spawn." int default="1"
option "connections" c "Connections to establish per server." int default="1" option "connections" c "Connections to establish per server." int default="1"
option "depth" d "Maximum depth to pipeline requests." int default="1" option "depth" d "Maximum depth to pipeline requests." int default="1"
option "sasl" - "Perform a binary SASL authentication (plaintext) before \ #option "sasl" - "Perform a binary SASL authentication (plaintext) before \
issuing any requests. String format is user:pass (Note: this does NOT #issuing any requests. String format is user:pass (Note: this does NOT
automatically set --binary)" string #automatically set --binary)" string
option "roundrobin" R "Assign threads to servers in round-robin fashion. \ option "roundrobin" R "Assign threads to servers in round-robin fashion. \
By default, each thread connects to every server." By default, each thread connects to every server."

View File

@ -629,7 +629,6 @@ void do_mutilate(const vector<string>& servers, options_t& options,
for (int c = 0; c < options.connections; c++) { for (int c = 0; c < options.connections; c++) {
Connection* conn = new Connection(base, evdns, hostname, port, options, Connection* conn = new Connection(base, evdns, hostname, port, options,
args.binary_given || args.sasl_given,
args.agentmode_given ? false : args.agentmode_given ? false :
true); true);
connections.push_back(conn); connections.push_back(conn);
@ -652,36 +651,6 @@ void do_mutilate(const vector<string>& servers, options_t& options,
else break; else break;
} }
// Do SASL and wait to complete
if(args.sasl_given) {
char *user, *pass;
saveptr = NULL;
user = strtok_r(args.sasl_arg, ":", &saveptr);
pass = strtok_r(NULL, ":", &saveptr);
if (user == NULL)
DIE("strtok(.., \":\") failed to parse %s", args.sasl_arg);
for (Connection *conn: connections) {
conn->doPlaintextSASL(user, (pass == NULL) ? "" : pass);
}
while (1) {
// FIXME: If all connections become ready before event_base_loop
// is called, this will deadlock.
event_base_loop(base, EVLOOP_ONCE);
bool restart = false;
for (Connection *conn: connections)
if (conn->read_state != Connection::IDLE)
restart = true;
if (restart) continue;
else break;
}
}
// Load database on lead connection for each server. // Load database on lead connection for each server.
if (!options.noload) { if (!options.noload) {
V("Loading database."); V("Loading database.");
@ -899,6 +868,19 @@ void args_to_options(options_t* options) {
// else // else
options->records = args.records_arg / options->server_given; options->records = args.records_arg / options->server_given;
options->binary = args.binary_given;
options->sasl = args.username_given;
if (args.password_given)
strcpy(options->password, args.password_arg);
else
strcpy(options->password, "");
if (args.username_given)
strcpy(options->username, args.username_arg);
else
strcpy(options->username, "");
D("options->records = %d", options->records); D("options->records = %d", options->records);
if (!options->records) options->records = 1; if (!options->records) options->records = 1;