/*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "jsonrpc.h"
+#include <assert.h>
#include <errno.h>
#include "byteq.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
#include "json.h"
#include "list.h"
#include "ofpbuf.h"
#include "poll-loop.h"
#include "queue.h"
+#include "reconnect.h"
#include "stream.h"
+#include "timeval.h"
#define THIS_MODULE VLM_jsonrpc
#include "vlog.h"
static void jsonrpc_received(struct jsonrpc *);
static void jsonrpc_cleanup(struct jsonrpc *);
+/* This is just the same as stream_open() except that it uses the default
+ * JSONRPC ports if none is specified. */
+int
+jsonrpc_stream_open(const char *name, struct stream **streamp)
+{
+ return stream_open_with_default_ports(name, JSONRPC_TCP_PORT,
+ JSONRPC_SSL_PORT, streamp);
+}
+
+/* This is just the same as pstream_open() except that it uses the default
+ * JSONRPC ports if none is specified. */
+int
+jsonrpc_pstream_open(const char *name, struct pstream **pstreamp)
+{
+ return pstream_open_with_default_ports(name, JSONRPC_TCP_PORT,
+ JSONRPC_SSL_PORT, pstreamp);
+}
+
struct jsonrpc *
jsonrpc_open(struct stream *stream)
{
return;
}
+ stream_run(rpc->stream);
while (!queue_is_empty(&rpc->output)) {
struct ofpbuf *buf = rpc->output.head;
int retval;
void
jsonrpc_wait(struct jsonrpc *rpc)
{
- if (!rpc->status && !queue_is_empty(&rpc->output)) {
- stream_send_wait(rpc->stream);
+ if (!rpc->status) {
+ stream_run_wait(rpc->stream);
+ if (!queue_is_empty(&rpc->output)) {
+ stream_send_wait(rpc->stream);
+ }
}
}
return rpc->name;
}
+static void
+jsonrpc_log_msg(const struct jsonrpc *rpc, const char *title,
+ const struct jsonrpc_msg *msg)
+{
+ if (VLOG_IS_DBG_ENABLED()) {
+ struct ds s = DS_EMPTY_INITIALIZER;
+ if (msg->method) {
+ ds_put_format(&s, ", method=\"%s\"", msg->method);
+ }
+ if (msg->params) {
+ ds_put_cstr(&s, ", params=");
+ json_to_ds(msg->params, 0, &s);
+ }
+ if (msg->result) {
+ ds_put_cstr(&s, ", result=");
+ json_to_ds(msg->result, 0, &s);
+ }
+ if (msg->error) {
+ ds_put_cstr(&s, ", error=");
+ json_to_ds(msg->error, 0, &s);
+ }
+ if (msg->id) {
+ ds_put_cstr(&s, ", id=");
+ json_to_ds(msg->id, 0, &s);
+ }
+ VLOG_DBG("%s: %s %s%s", rpc->name, title,
+ jsonrpc_msg_type_to_string(msg->type), ds_cstr(&s));
+ ds_destroy(&s);
+ }
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
int
jsonrpc_send(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
{
return rpc->status;
}
+ jsonrpc_log_msg(rpc, "send", msg);
+
json = jsonrpc_msg_to_json(msg);
s = json_to_string(json, 0);
length = strlen(s);
}
}
+/* Always takes ownership of 'msg', regardless of success. */
int
jsonrpc_send_block(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
{
int error;
+ fatal_signal_run();
+
error = jsonrpc_send(rpc, msg);
if (error) {
return error;
}
- while (!queue_is_empty(&rpc->output) && !rpc->status) {
+ for (;;) {
jsonrpc_run(rpc);
+ if (queue_is_empty(&rpc->output) || rpc->status) {
+ return rpc->status;
+ }
jsonrpc_wait(rpc);
poll_block();
}
- return rpc->status;
}
int
for (;;) {
int error = jsonrpc_recv(rpc, msgp);
if (error != EAGAIN) {
+ fatal_signal_run();
return error;
}
}
}
+/* Always takes ownership of 'request', regardless of success. */
+int
+jsonrpc_transact_block(struct jsonrpc *rpc, struct jsonrpc_msg *request,
+ struct jsonrpc_msg **replyp)
+{
+ struct jsonrpc_msg *reply = NULL;
+ struct json *id;
+ int error;
+
+ id = json_clone(request->id);
+ error = jsonrpc_send_block(rpc, request);
+ if (!error) {
+ for (;;) {
+ error = jsonrpc_recv_block(rpc, &reply);
+ if (error
+ || (reply->type == JSONRPC_REPLY
+ && json_equal(id, reply->id))) {
+ break;
+ }
+ jsonrpc_msg_destroy(reply);
+ }
+ }
+ *replyp = error ? NULL : reply;
+ json_destroy(id);
+ return error;
+}
+
static void
jsonrpc_received(struct jsonrpc *rpc)
{
return;
}
+ jsonrpc_log_msg(rpc, "received", msg);
rpc->received = msg;
}
}
struct jsonrpc_msg *
-jsonrpc_create_request(const char *method, struct json *params)
+jsonrpc_create_request(const char *method, struct json *params,
+ struct json **idp)
{
- return jsonrpc_create(JSONRPC_REQUEST, method, params, NULL, NULL,
- jsonrpc_create_id());
+ struct json *id = jsonrpc_create_id();
+ if (idp) {
+ *idp = json_clone(id);
+ }
+ return jsonrpc_create(JSONRPC_REQUEST, method, params, NULL, NULL, id);
}
struct jsonrpc_msg *
return json;
}
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session {
+ struct reconnect *reconnect;
+ struct jsonrpc *rpc;
+ struct stream *stream;
+ struct pstream *pstream;
+ unsigned int seqno;
+};
+
+/* Creates and returns a jsonrpc_session to 'name', which should be a string
+ * acceptable to stream_open() or pstream_open().
+ *
+ * If 'name' is an active connection method, e.g. "tcp:127.1.2.3", the new
+ * jsonrpc_session connects and reconnects, with back-off, to 'name'.
+ *
+ * If 'name' is a passive connection method, e.g. "ptcp:", the new
+ * jsonrpc_session listens for connections to 'name'. It maintains at most one
+ * connection at any given time. Any new connection causes the previous one
+ * (if any) to be dropped. */
+struct jsonrpc_session *
+jsonrpc_session_open(const char *name)
+{
+ struct jsonrpc_session *s;
+
+ s = xmalloc(sizeof *s);
+ s->reconnect = reconnect_create(time_msec());
+ reconnect_set_name(s->reconnect, name);
+ reconnect_enable(s->reconnect, time_msec());
+ s->rpc = NULL;
+ s->stream = NULL;
+ s->pstream = NULL;
+ s->seqno = 0;
+
+ if (!pstream_verify_name(name)) {
+ reconnect_set_passive(s->reconnect, true, time_msec());
+ }
+
+ return s;
+}
+
+/* Creates and returns a jsonrpc_session that is initially connected to
+ * 'jsonrpc'. If the connection is dropped, it will not be reconnected. */
+struct jsonrpc_session *
+jsonrpc_session_open_unreliably(struct jsonrpc *jsonrpc)
+{
+ struct jsonrpc_session *s;
+
+ s = xmalloc(sizeof *s);
+ s->reconnect = reconnect_create(time_msec());
+ reconnect_set_name(s->reconnect, jsonrpc_get_name(jsonrpc));
+ reconnect_set_max_tries(s->reconnect, 0);
+ reconnect_connected(s->reconnect, time_msec());
+ s->rpc = jsonrpc;
+ s->stream = NULL;
+ s->pstream = NULL;
+ s->seqno = 0;
+
+ return s;
+}
+
+void
+jsonrpc_session_close(struct jsonrpc_session *s)
+{
+ if (s) {
+ jsonrpc_close(s->rpc);
+ reconnect_destroy(s->reconnect);
+ stream_close(s->stream);
+ pstream_close(s->pstream);
+ free(s);
+ }
+}
+
+static void
+jsonrpc_session_disconnect(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_error(s->rpc, EOF);
+ jsonrpc_close(s->rpc);
+ s->rpc = NULL;
+ s->seqno++;
+ } else if (s->stream) {
+ stream_close(s->stream);
+ s->stream = NULL;
+ s->seqno++;
+ }
+}
+
+static void
+jsonrpc_session_connect(struct jsonrpc_session *s)
+{
+ const char *name = reconnect_get_name(s->reconnect);
+ int error;
+
+ jsonrpc_session_disconnect(s);
+ if (!reconnect_is_passive(s->reconnect)) {
+ error = jsonrpc_stream_open(name, &s->stream);
+ if (!error) {
+ reconnect_connecting(s->reconnect, time_msec());
+ }
+ } else {
+ error = s->pstream ? 0 : jsonrpc_pstream_open(name, &s->pstream);
+ if (!error) {
+ reconnect_listening(s->reconnect, time_msec());
+ }
+ }
+
+ if (error) {
+ reconnect_connect_failed(s->reconnect, time_msec(), error);
+ }
+ s->seqno++;
+}
+
+void
+jsonrpc_session_run(struct jsonrpc_session *s)
+{
+ if (s->pstream) {
+ struct stream *stream;
+ int error;
+
+ error = pstream_accept(s->pstream, &stream);
+ if (!error) {
+ if (s->rpc || s->stream) {
+ VLOG_INFO_RL(&rl,
+ "%s: new connection replacing active connection",
+ reconnect_get_name(s->reconnect));
+ jsonrpc_session_disconnect(s);
+ }
+ reconnect_connected(s->reconnect, time_msec());
+ s->rpc = jsonrpc_open(stream);
+ } else if (error != EAGAIN) {
+ reconnect_listen_error(s->reconnect, time_msec(), error);
+ pstream_close(s->pstream);
+ s->pstream = NULL;
+ }
+ }
+
+ if (s->rpc) {
+ int error;
+
+ jsonrpc_run(s->rpc);
+ error = jsonrpc_get_status(s->rpc);
+ if (error) {
+ reconnect_disconnected(s->reconnect, time_msec(), 0);
+ jsonrpc_session_disconnect(s);
+ }
+ } else if (s->stream) {
+ int error;
+
+ stream_run(s->stream);
+ error = stream_connect(s->stream);
+ if (!error) {
+ reconnect_connected(s->reconnect, time_msec());
+ s->rpc = jsonrpc_open(s->stream);
+ s->stream = NULL;
+ } else if (error != EAGAIN) {
+ reconnect_connect_failed(s->reconnect, time_msec(), error);
+ stream_close(s->stream);
+ s->stream = NULL;
+ }
+ }
+
+ switch (reconnect_run(s->reconnect, time_msec())) {
+ case RECONNECT_CONNECT:
+ jsonrpc_session_connect(s);
+ break;
+
+ case RECONNECT_DISCONNECT:
+ reconnect_disconnected(s->reconnect, time_msec(), 0);
+ jsonrpc_session_disconnect(s);
+ break;
+
+ case RECONNECT_PROBE:
+ if (s->rpc) {
+ struct json *params;
+ struct jsonrpc_msg *request;
+
+ params = json_array_create_empty();
+ request = jsonrpc_create_request("echo", params, NULL);
+ json_destroy(request->id);
+ request->id = json_string_create("echo");
+ jsonrpc_send(s->rpc, request);
+ }
+ break;
+ }
+}
+
+void
+jsonrpc_session_wait(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_wait(s->rpc);
+ } else if (s->stream) {
+ stream_run_wait(s->stream);
+ stream_connect_wait(s->stream);
+ }
+ if (s->pstream) {
+ pstream_wait(s->pstream);
+ }
+ reconnect_wait(s->reconnect, time_msec());
+}
+
+size_t
+jsonrpc_session_get_backlog(const struct jsonrpc_session *s)
+{
+ return s->rpc ? jsonrpc_get_backlog(s->rpc) : 0;
+}
+
+const char *
+jsonrpc_session_get_name(const struct jsonrpc_session *s)
+{
+ return reconnect_get_name(s->reconnect);
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg)
+{
+ if (s->rpc) {
+ return jsonrpc_send(s->rpc, msg);
+ } else {
+ jsonrpc_msg_destroy(msg);
+ return ENOTCONN;
+ }
+}
+
+struct jsonrpc_msg *
+jsonrpc_session_recv(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ struct jsonrpc_msg *msg;
+ jsonrpc_recv(s->rpc, &msg);
+ if (msg) {
+ reconnect_received(s->reconnect, time_msec());
+ if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+ /* Echo request. Send reply. */
+ struct jsonrpc_msg *reply;
+
+ reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+ jsonrpc_session_send(s, reply);
+ } else if (msg->type == JSONRPC_REPLY
+ && msg->id && msg->id->type == JSON_STRING
+ && !strcmp(msg->id->u.string, "echo")) {
+ /* It's a reply to our echo request. Suppress it. */
+ } else {
+ return msg;
+ }
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+ return NULL;
+}
+
+void
+jsonrpc_session_recv_wait(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_recv_wait(s->rpc);
+ }
+}
+
+bool
+jsonrpc_session_is_alive(const struct jsonrpc_session *s)
+{
+ return s->rpc || s->stream || reconnect_get_max_tries(s->reconnect);
+}
+
+bool
+jsonrpc_session_is_connected(const struct jsonrpc_session *s)
+{
+ return s->rpc != NULL;
+}
+
+unsigned int
+jsonrpc_session_get_seqno(const struct jsonrpc_session *s)
+{
+ return s->seqno;
+}
+
+void
+jsonrpc_session_force_reconnect(struct jsonrpc_session *s)
+{
+ reconnect_force_reconnect(s->reconnect, time_msec());
+}