/* Reconnect to OVS DB and call the OVS DB post connection init callback * if connection has been established. */ static void ovs_db_reconnect(ovs_db_t *pdb) { const char *node_info = pdb->node; struct addrinfo *result; if (pdb->unix_path[0] != '\0') { /* use UNIX socket instead of INET address */ node_info = pdb->unix_path; result = calloc(1, sizeof(struct addrinfo)); struct sockaddr_un *sa_unix = calloc(1, sizeof(struct sockaddr_un)); if (result == NULL || sa_unix == NULL) { sfree(result); sfree(sa_unix); return; } result->ai_family = AF_UNIX; result->ai_socktype = SOCK_STREAM; result->ai_addrlen = sizeof(*sa_unix); result->ai_addr = (struct sockaddr *)sa_unix; sa_unix->sun_family = result->ai_family; sstrncpy(sa_unix->sun_path, pdb->unix_path, sizeof(sa_unix->sun_path)); } else { /* inet socket address */ struct addrinfo hints; /* setup criteria for selecting the socket address */ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* get socket addresses */ int ret = getaddrinfo(pdb->node, pdb->service, &hints, &result); if (ret != 0) { OVS_ERROR("getaddrinfo(): %s", gai_strerror(ret)); return; } } /* try to connect to the server */ for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) { int sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock < 0) { OVS_DEBUG("socket(): %s", STRERRNO); continue; } if (connect(sock, rp->ai_addr, rp->ai_addrlen) < 0) { close(sock); OVS_DEBUG("connect(): %s [family=%d]", STRERRNO, rp->ai_family); } else { /* send notification to event thread */ pdb->sock = sock; ovs_db_event_post(pdb, OVS_DB_EVENT_CONN_ESTABLISHED); break; } } if (pdb->sock < 0) OVS_ERROR("connect to \"%s\" failed", node_info); freeaddrinfo(result); }
/* Push raw data into into the JSON reader for processing */ static int ovs_json_reader_push_data(ovs_json_reader_t *jreader, const char *data, size_t data_len) { char *new_buff = NULL; size_t available = jreader->buff_size - jreader->buff_offset; /* check/update required memory space */ if (available < data_len) { OVS_DEBUG("Reallocate buffer [size=%d, available=%d required=%d]", (int)jreader->buff_size, (int)available, (int)data_len); /* allocate new chunk of memory */ new_buff = realloc(jreader->buff_ptr, (jreader->buff_size + data_len)); if (new_buff == NULL) return -1; /* point to new allocated memory */ jreader->buff_ptr = new_buff; jreader->buff_size += data_len; } /* store input data */ memcpy(jreader->buff_ptr + jreader->buff_offset, data, data_len); jreader->buff_offset += data_len; return 0; }
/* Handle JSON data (one request) and call * appropriate event OVS DB handler. Currently, * update callback 'ovs_db_table_update_cb' and * result callback 'ovs_db_result_cb' is supported. */ static int ovs_db_json_data_process(ovs_db_t *pdb, const char *data, size_t len) { const char *method = NULL; char yajl_errbuf[OVS_YAJL_ERROR_BUFFER_SIZE]; const char *method_path[] = {"method", NULL}; const char *result_path[] = {"result", NULL}; char *sjson = NULL; yajl_val jnode, jval; /* duplicate the data to make null-terminated string * required for yajl_tree_parse() */ if ((sjson = calloc(1, len + 1)) == NULL) return -1; sstrncpy(sjson, data, len + 1); OVS_DEBUG("[len=%" PRIsz "] %s", len, sjson); /* parse json data */ jnode = yajl_tree_parse(sjson, yajl_errbuf, sizeof(yajl_errbuf)); if (jnode == NULL) { OVS_ERROR("yajl_tree_parse() %s", yajl_errbuf); sfree(sjson); return -1; } /* get method name */ if ((jval = yajl_tree_get(jnode, method_path, yajl_t_string)) != NULL) { if ((method = YAJL_GET_STRING(jval)) == NULL) { yajl_tree_free(jnode); sfree(sjson); return -1; } if (strcmp("echo", method) == 0) { /* echo request from the server */ if (ovs_db_table_echo_cb(pdb, jnode) < 0) OVS_ERROR("handle echo request failed"); } else if (strcmp("update", method) == 0) { /* update notification */ if (ovs_db_table_update_cb(pdb, jnode) < 0) OVS_ERROR("handle update notification failed"); } } else if ((jval = yajl_tree_get(jnode, result_path, yajl_t_any)) != NULL) { /* result notification */ if (ovs_db_result_cb(pdb, jnode) < 0) OVS_ERROR("handle result reply failed"); } else OVS_ERROR("connot find method or result failed"); /* release memory */ yajl_tree_free(jnode); sfree(sjson); return 0; }
/* OVS DB echo request handler. When OVS DB sends * "echo" request to the client, client should generate * "echo" replay with the same content received in the * request */ static int ovs_db_table_echo_cb(const ovs_db_t *pdb, yajl_val jnode) { yajl_val jparams; yajl_val jid; yajl_gen jgen; size_t resp_len = 0; const char *resp = NULL; const char *params_path[] = {"params", NULL}; const char *id_path[] = {"id", NULL}; yajl_gen_status yajl_gen_ret; if ((jgen = yajl_gen_alloc(NULL)) == NULL) return -1; /* check & get request attributes */ if ((jparams = yajl_tree_get(jnode, params_path, yajl_t_array)) == NULL || ((jid = yajl_tree_get(jnode, id_path, yajl_t_any)) == NULL)) { OVS_ERROR("parse echo request failed"); goto yajl_gen_failure; } /* generate JSON echo response */ OVS_YAJL_CALL(yajl_gen_map_open, jgen); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "result"); OVS_YAJL_CALL(ovs_yajl_gen_val, jgen, jparams); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "error"); OVS_YAJL_CALL(yajl_gen_null, jgen); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "id"); OVS_YAJL_CALL(ovs_yajl_gen_val, jgen, jid); OVS_YAJL_CALL(yajl_gen_map_close, jgen); OVS_YAJL_CALL(yajl_gen_get_buf, jgen, (const unsigned char **)&resp, &resp_len); /* send the response */ OVS_DEBUG("response: %s", resp); if (ovs_db_data_send(pdb, resp, resp_len) < 0) { OVS_ERROR("send echo reply failed"); goto yajl_gen_failure; } /* clean up and return success */ yajl_gen_clear(jgen); return 0; yajl_gen_failure: /* release memory */ yajl_gen_clear(jgen); return -1; }
/* POLL worker thread. * It listens on OVS DB connection for incoming * requests/reply/events etc. Also, it reconnects to OVS DB * if connection has been lost. */ static void *ovs_poll_worker(void *arg) { ovs_db_t *pdb = (ovs_db_t *)arg; /* pointer to OVS DB */ ovs_json_reader_t *jreader = NULL; struct pollfd poll_fd = { .fd = pdb->sock, .events = POLLIN | POLLPRI, .revents = 0, }; /* create JSON reader instance */ if ((jreader = ovs_json_reader_alloc()) == NULL) { OVS_ERROR("initialize json reader failed"); return NULL; } /* poll data */ while (ovs_db_poll_is_running(pdb)) { poll_fd.fd = pdb->sock; int poll_ret = poll(&poll_fd, 1, /* ms */ OVS_DB_POLL_TIMEOUT * 1000); if (poll_ret < 0) { OVS_ERROR("poll(): %s", STRERRNO); break; } else if (poll_ret == 0) { OVS_DEBUG("poll(): timeout"); if (pdb->sock < 0) /* invalid fd, so try to reconnect */ ovs_db_reconnect(pdb); continue; } if (poll_fd.revents & POLLNVAL) { /* invalid file descriptor, clean-up */ ovs_db_callback_remove_all(pdb); ovs_json_reader_reset(jreader); /* setting poll FD to -1 tells poll() call to ignore this FD. * In that case poll() call will return timeout all the time */ pdb->sock = (-1); } else if ((poll_fd.revents & POLLERR) || (poll_fd.revents & POLLHUP)) { /* connection is broken */ close(poll_fd.fd); ovs_db_event_post(pdb, OVS_DB_EVENT_CONN_TERMINATED); OVS_ERROR("poll() peer closed its end of the channel"); } else if ((poll_fd.revents & POLLIN) || (poll_fd.revents & POLLPRI)) { /* read incoming data */ char buff[OVS_DB_POLL_READ_BLOCK_SIZE]; ssize_t nbytes = recv(poll_fd.fd, buff, sizeof(buff), 0); if (nbytes < 0) { OVS_ERROR("recv(): %s", STRERRNO); /* read error? Try to reconnect */ close(poll_fd.fd); continue; } else if (nbytes == 0) { close(poll_fd.fd); ovs_db_event_post(pdb, OVS_DB_EVENT_CONN_TERMINATED); OVS_ERROR("recv() peer has performed an orderly shutdown"); continue; } /* read incoming data */ size_t json_len = 0; const char *json = NULL; OVS_DEBUG("recv(): received %zd bytes of data", nbytes); ovs_json_reader_push_data(jreader, buff, nbytes); while (!ovs_json_reader_pop(jreader, &json, &json_len)) /* process JSON data */ ovs_db_json_data_process(pdb, json, json_len); } } OVS_DEBUG("poll thread has been completed"); ovs_json_reader_free(jreader); return NULL; } /* EVENT worker thread. * Perform task based on incoming events. This * task can be done asynchronously which allows to * handle OVS DB callback like 'init_cb'. */ static void *ovs_event_worker(void *arg) { ovs_db_t *pdb = (ovs_db_t *)arg; while (pdb->event_thread.value != OVS_DB_EVENT_TERMINATE) { /* wait for an event */ struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += (OVS_DB_EVENT_TIMEOUT); int ret = pthread_cond_timedwait(&pdb->event_thread.cond, &pdb->event_thread.mutex, &ts); if (!ret || ret == ETIMEDOUT) { /* handle the event */ OVS_DEBUG("handle event %d", pdb->event_thread.value); switch (pdb->event_thread.value) { case OVS_DB_EVENT_CONN_ESTABLISHED: if (pdb->cb.post_conn_init) pdb->cb.post_conn_init(pdb); /* reset event */ pdb->event_thread.value = OVS_DB_EVENT_NONE; break; case OVS_DB_EVENT_CONN_TERMINATED: if (pdb->cb.post_conn_terminate) pdb->cb.post_conn_terminate(); /* reset event */ pdb->event_thread.value = OVS_DB_EVENT_NONE; break; case OVS_DB_EVENT_NONE: /* wait timeout */ OVS_DEBUG("no event received (timeout)"); break; default: OVS_DEBUG("unknown event received"); break; } } else { /* unexpected error */ OVS_ERROR("pthread_cond_timedwait() failed"); break; } } OVS_DEBUG("event thread has been completed"); return NULL; }
int ovs_db_send_request(ovs_db_t *pdb, const char *method, const char *params, ovs_db_result_cb_t cb) { int ret = 0; yajl_gen_status yajl_gen_ret; yajl_val jparams; yajl_gen jgen; ovs_callback_t *new_cb = NULL; uint64_t uid; char uid_buff[OVS_UID_STR_SIZE]; const char *req = NULL; size_t req_len = 0; struct timespec ts; /* sanity check */ if (!pdb || !method || !params) return -1; if ((jgen = yajl_gen_alloc(NULL)) == NULL) return -1; /* try to parse params */ if ((jparams = yajl_tree_parse(params, NULL, 0)) == NULL) { OVS_ERROR("params is not a JSON string"); yajl_gen_clear(jgen); return -1; } /* generate method field */ OVS_YAJL_CALL(yajl_gen_map_open, jgen); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "method"); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, method); /* generate params field */ OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "params"); OVS_YAJL_CALL(ovs_yajl_gen_val, jgen, jparams); yajl_tree_free(jparams); /* generate id field */ OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, "id"); uid = ovs_uid_generate(); snprintf(uid_buff, sizeof(uid_buff), "%" PRIX64, uid); OVS_YAJL_CALL(ovs_yajl_gen_tstring, jgen, uid_buff); OVS_YAJL_CALL(yajl_gen_map_close, jgen); if (cb) { /* register result callback */ if ((new_cb = calloc(1, sizeof(*new_cb))) == NULL) goto yajl_gen_failure; /* add new callback to front */ sem_init(&new_cb->result.sync, 0, 0); new_cb->result.call = cb; new_cb->uid = uid; ovs_db_callback_add(pdb, new_cb); } /* send the request */ OVS_YAJL_CALL(yajl_gen_get_buf, jgen, (const unsigned char **)&req, &req_len); OVS_DEBUG("%s", req); if (!ovs_db_data_send(pdb, req, req_len)) { if (cb) { /* wait for result */ clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += OVS_DB_SEND_REQ_TIMEOUT; if (sem_timedwait(&new_cb->result.sync, &ts) < 0) { OVS_ERROR("%s() no replay received within %d sec", __FUNCTION__, OVS_DB_SEND_REQ_TIMEOUT); ret = (-1); } } } else { OVS_ERROR("ovs_db_data_send() failed"); ret = (-1); } yajl_gen_failure: if (new_cb) { /* destroy callback */ sem_destroy(&new_cb->result.sync); ovs_db_callback_remove(pdb, new_cb); } /* release memory */ yajl_gen_clear(jgen); return (yajl_gen_ret != yajl_gen_status_ok) ? (-1) : ret; }