static int execute_commands(sdb_client_t *client, sdb_llist_t *commands) { sdb_llist_iter_t *iter; int status = 0; iter = sdb_llist_get_iter(commands); if (! iter) { sdb_log(SDB_LOG_ERR, "Failed to iterate commands"); return 1; } while (sdb_llist_iter_has_next(iter)) { sdb_object_t *obj = sdb_llist_iter_get_next(iter); if (sdb_client_send(client, SDB_CONNECTION_QUERY, (uint32_t)strlen(obj->name), obj->name) <= 0) { sdb_log(SDB_LOG_ERR, "Failed to send command '%s' to server", obj->name); status = 1; break; } /* Wait for server replies. We might get any number of log messages * but eventually see the reply to the query, which is either DATA or * ERROR. */ while (42) { status = sdb_command_print_reply(client); if (status < 0) { sdb_log(SDB_LOG_ERR, "Failed to read reply from server"); break; } if ((status == SDB_CONNECTION_DATA) || (status == SDB_CONNECTION_ERROR)) break; if (status == SDB_CONNECTION_OK) { /* pre 0.4 versions used OK instead of DATA */ sdb_log(SDB_LOG_WARNING, "Received unexpected OK status from " "server in response to a QUERY (expected DATA); " "assuming we're talking to an old server"); break; } } if ((status != SDB_CONNECTION_OK) && (status != SDB_CONNECTION_DATA)) break; /* error */ } sdb_llist_iter_destroy(iter); return status; } /* execute_commands */
static int mock_collect(sdb_object_t *user_data) { size_t i; int check; if (SDB_OBJ_WRAPPER(user_data)->data != MAGIC_DATA) { sdb_log(SDB_LOG_ERR, "mock::plugin: Invalid user data %p " "passed to collect", SDB_OBJ_WRAPPER(user_data)->data); exit(1); } for (i = 0; i < SDB_STATIC_ARRAY_LEN(hostnames); ++i) { if ((check = sdb_plugin_store_host(hostnames[i], sdb_gettime()))) { sdb_log(SDB_LOG_ERR, "mock::plugin: Failed to store host: " "status %d", check); exit(1); } } for (i = 0; i < SDB_STATIC_ARRAY_LEN(metrics); ++i) { if ((check = sdb_plugin_store_metric(metrics[i].hostname, metrics[i].metric, &metrics[i].store, sdb_gettime()))) { sdb_log(SDB_LOG_ERR, "mock::plugin: Failed to store metric: " "status %d", check); exit(1); } } for (i = 0; i < SDB_STATIC_ARRAY_LEN(services); ++i) { if ((check = sdb_plugin_store_service(services[i].hostname, services[i].service, sdb_gettime()))) { sdb_log(SDB_LOG_ERR, "mock::plugin: Failed to store service: " "status %d", check); exit(1); } } for (i = 0; i < SDB_STATIC_ARRAY_LEN(attributes); ++i) { sdb_data_t datum = { SDB_TYPE_STRING, { .string = NULL } }; datum.data.string = strdup(attributes[i].value); if ((check = sdb_plugin_store_attribute(attributes[i].hostname, attributes[i].name, &datum, sdb_gettime()))) { sdb_log(SDB_LOG_ERR, "mock::plugin: Failed to store attribute: " "status %d", check); exit(1); } free(datum.data.string); } return 0; } /* mock_collect */
int sdb_unixsock_client_connect(sdb_unixsock_client_t *client) { struct sockaddr_un sa; int fd; if ((! client) || (! client->path)) return -1; memset(&sa, 0, sizeof(sa)); if (client->fh) fclose(client->fh); fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0); if (fd < 0) { char errbuf[1024]; sdb_log(SDB_LOG_ERR, "unixsock: Failed to open socket: %s", sdb_strerror(errno, errbuf, sizeof(errbuf))); return -1; } sa.sun_family = AF_UNIX; strncpy(sa.sun_path, client->path, sizeof(sa.sun_path)); sa.sun_path[sizeof(sa.sun_path) - 1] = '\0'; if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) { char errbuf[1024]; sdb_log(SDB_LOG_ERR, "unixsock: Failed to connect to %s: %s", sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf))); close(fd); return -1; } client->fh = fdopen(fd, "r+"); if (! client->fh) { char errbuf[1024]; sdb_log(SDB_LOG_ERR, "unixsock: Failed to open I/O " "stream for %s: %s", sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf))); close(fd); return -1; } /* enable line-buffering */ setvbuf(client->fh, NULL, _IOLBF, 0); client->shutdown = 0; return 0; } /* sdb_unixsock_client_connect */
static sdb_timeseries_info_t * mock_describe_ts(const char *id, sdb_object_t *user_data) { if (*id != '/') { sdb_log(SDB_LOG_ERR, "mock::timeseries: Invalid time-series %s", id); exit(1); } if (SDB_OBJ_WRAPPER(user_data)->data != MAGIC_DATA) { sdb_log(SDB_LOG_ERR, "mock::timeseries: Invalid user data %p " "passed to collect", SDB_OBJ_WRAPPER(user_data)->data); exit(1); } return sdb_timeseries_info_create(SDB_STATIC_ARRAY_LEN(names), names); } /* mock_describe_ts */
static void connection_destroy(sdb_object_t *obj) { sdb_conn_t *conn; size_t len; assert(obj); conn = CONN(obj); conn->ready = 0; if (conn->finish) conn->finish(conn); conn->finish = NULL; if (conn->buf) { len = sdb_strbuf_len(conn->buf); if (len) sdb_log(SDB_LOG_INFO, "frontend: Discarding incomplete command " "(%zu byte%s left in buffer)", len, len == 1 ? "" : "s"); } if (conn->client_addr.ss_family == AF_UNIX) { sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection %s from peer %s", obj->name, conn->username); } else { char host[1024] = "<unknown>", port[32] = ""; getnameinfo((struct sockaddr *)&conn->client_addr, conn->client_addr_len, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection %s from peer %s " "at %s:%s", obj->name, conn->username, host, port); } sdb_connection_close(conn); if (conn->username) free(conn->username); conn->username = NULL; sdb_strbuf_destroy(conn->buf); conn->buf = NULL; sdb_strbuf_destroy(conn->errbuf); conn->errbuf = NULL; } /* connection_destroy */
static bool rrdcached_connect(char *addr) { #ifdef HAVE_RRD_CLIENT_H rrd_clear_error(); if (! rrdc_is_connected(addr)) { if (rrdc_connect(addr)) { sdb_log(SDB_LOG_ERR, "Failed to connectd to RRDCacheD at %s: %s", addr, rrd_get_error()); return 0; } } #else sdb_log(SDB_LOG_ERR, "Callback called with RRDCacheD address " "but your build of SysDB does not support that"); return 0; #endif return 1; } /* rrdcached_connect */
static int mock_shutdown(sdb_object_t *user_data) { if (SDB_OBJ_WRAPPER(user_data)->data != MAGIC_DATA) { sdb_log(SDB_LOG_ERR, "mock::plugin: Invalid user data %p " "passed to shutdown", SDB_OBJ_WRAPPER(user_data)->data); exit(1); } return 0; } /* mock_shutdown */
static int sdb_unixsock_client_process_one_line(sdb_unixsock_client_t *client, char *line, sdb_unixsock_client_data_cb callback, sdb_object_t *user_data, const char *delim, int column_count, int *types) { sdb_data_t data[column_count]; char *orig_line = line; int i; assert(column_count > 0); for (i = 0; i < column_count; ++i) { char *next; if (! line) { /* this must not happen */ sdb_log(SDB_LOG_ERR, "unixsock: Unexpected EOL while " "parsing line (expected %i columns delimited by '%s'; " "got %i): %s", column_count, delim, /* last line number */ i, orig_line); return -1; } if ((delim[0] == '\0') || (delim[1] == '\0')) next = strchr(line, (int)delim[0]); else next = strpbrk(line, delim); if (next) { *next = '\0'; ++next; } if (types && (types[i] != SDB_TYPE_STRING)) { if (sdb_data_parse(line, types[i], &data[i])) return -1; } else { /* Avoid a copy in common cases */ data[i].type = SDB_TYPE_STRING; data[i].data.string = line; } line = next; } if (callback(client, (size_t)column_count, data, user_data)) return -1; for (i = 0; i < column_count; ++i) if (types && (types[i] != SDB_TYPE_STRING)) sdb_data_free_datum(&data[i]); return 0; } /* sdb_unixsock_client_process_one_line */
static int copy_data(sdb_timeseries_t *ts, rrd_value_t *data, time_t step, size_t ds_cnt, char **ds_names) { time_t start = SDB_TIME_TO_SECS(ts->start); time_t end = SDB_TIME_TO_SECS(ts->end); time_t t; ssize_t ds_target[ds_cnt]; size_t i, j; /* determine the target index of each data-source, * or -1 if the data-source isn't wanted */ for (i = 0; i < ds_cnt; i++) { ds_target[i] = -1; for (j = 0; j < ts->data_names_len; j++) { if (!strcmp(ds_names[i], ts->data_names[j])) { ds_target[i] = j; break; } } } /* check if any wanted data-source is missing */ for (i = 0; i < ts->data_names_len; i++) { bool found = false; for (j = 0; j < ds_cnt; j++) { if (!strcmp(ts->data_names[i], ds_names[j])) { found = true; break; } } if (!found) { sdb_log(SDB_LOG_ERR, "Requested data-source '%s' not found", ts->data_names[i]); return -1; } } for (t = start; t <= end; t += step) { i = (size_t)(t - start) / (size_t)step; for (j = 0; j < ds_cnt; ++j) { if (ds_target[j] >= 0) { size_t x = (size_t)ds_target[j]; ts->data[x][i].timestamp = SECS_TO_SDB_TIME(t); ts->data[x][i].value = *data; } ++data; } } return 0; }
int sdb_module_init(sdb_plugin_info_t *info) { sdb_object_t *user_data; sdb_plugin_set_info(info, SDB_PLUGIN_INFO_DESC, "a mock timeseries fetcher"); sdb_plugin_set_info(info, SDB_PLUGIN_INFO_COPYRIGHT, "Copyright (C) 2012 Sebastian 'tokkee' Harl <*****@*****.**>"); sdb_plugin_set_info(info, SDB_PLUGIN_INFO_LICENSE, "BSD"); sdb_plugin_set_info(info, SDB_PLUGIN_INFO_VERSION, SDB_VERSION); sdb_plugin_set_info(info, SDB_PLUGIN_INFO_PLUGIN_VERSION, SDB_VERSION); user_data = sdb_object_create_wrapper("mock_data", MAGIC_DATA, NULL); if (! user_data) { sdb_log(SDB_LOG_ERR, "mock::plugin: Failed to allocate user data"); exit(1); } sdb_plugin_register_timeseries_fetcher("mock", &mock_fetcher, user_data); sdb_object_deref(user_data); return 0; } /* sdb_module_init */
int sdb_unixsock_client_send(sdb_unixsock_client_t *client, const char *msg) { int status; if ((! client) || (! client->fh)) return -1; if (client->shutdown & SDB_SHUT_WR) /* reconnect */ sdb_unixsock_client_connect(client); status = fprintf(client->fh, "%s\r\n", msg); if (status < 0) { char errbuf[1024]; sdb_log(SDB_LOG_ERR, "unixsock: Failed to write to " "socket (%s): %s", client->path, sdb_strerror(errno, errbuf, sizeof(errbuf))); return status; } return status; } /* sdb_unixsock_client_send */
char * sdb_unixsock_client_recv(sdb_unixsock_client_t *client, char *buffer, size_t buflen) { char *tmp; if ((! client) || (! client->fh) || (! buffer)) return NULL; if (client->shutdown & SDB_SHUT_RD) /* reconnect */ sdb_unixsock_client_connect(client); tmp = NULL; while (tmp == NULL) { errno = 0; tmp = fgets(buffer, (int)buflen - 1, client->fh); if (! tmp) { if ((errno == EAGAIN) || (errno == EINTR)) continue; if (! feof(client->fh)) { char errbuf[1024]; sdb_log(SDB_LOG_ERR, "unixsock: Failed to read " "from socket (%s): %s", client->path, sdb_strerror(errno, errbuf, sizeof(errbuf))); } return NULL; } } buffer[buflen - 1] = '\0'; buflen = strlen(buffer); while (buflen && ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r'))) { buffer[buflen - 1] = '\0'; --buflen; } return buffer; } /* sdb_unixsock_client_recv */
int sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client, sdb_unixsock_client_data_cb callback, sdb_object_t *user_data, long int max_lines, const char *delim, int n_cols, ...) { int *types = NULL; int success = 0; if ((! client) || (! client->fh) || (! callback)) return -1; if (n_cols > 0) { va_list ap; int i; types = calloc((size_t)n_cols, sizeof(*types)); if (! types) return -1; va_start(ap, n_cols); for (i = 0; i < n_cols; ++i) { types[i] = va_arg(ap, int); if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) { sdb_log(SDB_LOG_ERR, "unixsock: Unknown column " "type %i while processing response from the " "UNIX socket @ %s.", types[i], client->path); va_end(ap); free(types); return -1; } } va_end(ap); }
int main(int argc, char **argv) { const char *host = NULL; char *homedir; char hist_file[1024] = ""; sdb_input_t input = SDB_INPUT_INIT; sdb_llist_t *commands = NULL; while (42) { int opt = getopt(argc, argv, "H:U:c:C:K:A:hV"); if (-1 == opt) break; switch (opt) { case 'H': host = optarg; break; case 'U': input.user = optarg; break; case 'c': { sdb_object_t *obj; if (! commands) commands = sdb_llist_create(); if (! commands) { sdb_log(SDB_LOG_ERR, "Failed to create list object"); exit(1); } if (! (obj = sdb_object_create_T(optarg, sdb_object_t))) { sdb_log(SDB_LOG_ERR, "Failed to create object"); exit(1); } if (sdb_llist_append(commands, obj)) { sdb_log(SDB_LOG_ERR, "Failed to append command to list"); sdb_object_deref(obj); exit(1); } sdb_object_deref(obj); } break; case 'C': ssl_options.cert_file = optarg; break; case 'K': ssl_options.key_file = optarg; break; case 'A': ssl_options.ca_file = optarg; break; case 'h': exit_usage(argv[0], 0); break; case 'V': exit_version(); break; default: exit_usage(argv[0], 1); } } if (optind < argc) exit_usage(argv[0], 1); if (! host) host = DEFAULT_SOCKET; if (! input.user) input.user = sdb_get_current_user(); else input.user = strdup(input.user); if (! input.user) exit(1); if (sdb_ssl_init()) exit(1); input.client = sdb_client_create(host); if (! input.client) { sdb_log(SDB_LOG_ERR, "Failed to create client object"); sdb_input_reset(&input); exit(1); } canonicalize_ssl_options(); if (sdb_client_set_ssl_options(input.client, &ssl_options)) { sdb_log(SDB_LOG_ERR, "Failed to apply SSL options"); sdb_input_reset(&input); sdb_ssl_free_options(&ssl_options); exit(1); } sdb_ssl_free_options(&ssl_options); if (sdb_client_connect(input.client, input.user)) { sdb_log(SDB_LOG_ERR, "Failed to connect to SysDBd"); sdb_input_reset(&input); exit(1); } if (commands) { int status = execute_commands(input.client, commands); sdb_llist_destroy(commands); sdb_input_reset(&input); if ((status != SDB_CONNECTION_OK) && (status != SDB_CONNECTION_DATA)) exit(1); exit(0); } sdb_log(SDB_LOG_INFO, "SysDB client "SDB_CLIENT_VERSION_STRING SDB_CLIENT_VERSION_EXTRA" (libsysdbclient %s%s)", sdb_client_version_string(), sdb_client_version_extra()); sdb_command_print_server_version(&input); printf("\n"); using_history(); if ((homedir = sdb_get_homedir())) { snprintf(hist_file, sizeof(hist_file) - 1, "%s/.sysdb_history", homedir); hist_file[sizeof(hist_file) - 1] = '\0'; free(homedir); homedir = NULL; errno = 0; if (read_history(hist_file) && (errno != ENOENT)) { char errbuf[1024]; sdb_log(SDB_LOG_WARNING, "Failed to load history (%s): %s", hist_file, sdb_strerror(errno, errbuf, sizeof(errbuf))); } } input.input = sdb_strbuf_create(2048); sdb_input_init(&input); sdb_input_mainloop(); sdb_client_shutdown(input.client, SHUT_WR); while (! sdb_client_eof(input.client)) { /* wait for remaining data to arrive */ sdb_command_print_reply(input.client); } if (hist_file[0] != '\0') { errno = 0; if (write_history(hist_file)) { char errbuf[1024]; sdb_log(SDB_LOG_WARNING, "Failed to store history (%s): %s", hist_file, sdb_strerror(errno, errbuf, sizeof(errbuf))); } } sdb_input_reset(&input); sdb_ssl_shutdown(); return 0; } /* main */
static int connection_init(sdb_object_t *obj, va_list ap) { sdb_conn_t *conn; int sock_fd; int sock_fl; assert(obj); conn = CONN(obj); sock_fd = va_arg(ap, int); conn->buf = sdb_strbuf_create(/* size = */ 128); if (! conn->buf) { sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate a read buffer " "for a new connection"); return -1; } conn->errbuf = sdb_strbuf_create(0); if (! conn->errbuf) { sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate an error buffer " "for a new connection"); return -1; } conn->client_addr_len = sizeof(conn->client_addr); conn->fd = accept(sock_fd, (struct sockaddr *)&conn->client_addr, &conn->client_addr_len); if (conn->fd < 0) { char buf[1024]; sdb_log(SDB_LOG_ERR, "frontend: Failed to accept remote " "connection: %s", sdb_strerror(errno, buf, sizeof(buf))); return -1; } /* update the object name */ snprintf(obj->name + strlen(CONN_FD_PREFIX), strlen(CONN_FD_PLACEHOLDER), "%i", conn->fd); /* defaults */ conn->read = conn_read; conn->write = conn_write; conn->finish = NULL; conn->ssl_session = NULL; sock_fl = fcntl(conn->fd, F_GETFL); if (fcntl(conn->fd, F_SETFL, sock_fl | O_NONBLOCK)) { char buf[1024]; sdb_log(SDB_LOG_ERR, "frontend: Failed to switch connection conn#%i " "to non-blocking mode: %s", conn->fd, sdb_strerror(errno, buf, sizeof(buf))); return -1; } conn->username = NULL; conn->ready = 0; sdb_log(SDB_LOG_DEBUG, "frontend: Accepted connection on fd=%i", conn->fd); conn->cmd = SDB_CONNECTION_IDLE; conn->cmd_len = 0; conn->skip_len = 0; return 0; } /* connection_init */
static sdb_timeseries_info_t * sdb_rrd_describe(const char *id, sdb_object_t *user_data) { rrd_info_t *info, *iter; char filename[strlen(id) + 1]; sdb_timeseries_info_t *ts_info; strncpy(filename, id, sizeof(filename)); if (user_data) { /* -> use RRDCacheD */ char *addr = SDB_OBJ_WRAPPER(user_data)->data; if (! rrdcached_connect(addr)) return NULL; #ifdef HAVE_RRD_CLIENT_H /* TODO: detect and use rrdc_info if possible */ sdb_log(SDB_LOG_ERR, "DESCRIBE not yet supported via RRDCacheD"); return NULL; #endif } else { rrd_clear_error(); info = rrd_info_r(filename); } if (! info) { sdb_log(SDB_LOG_ERR, "Failed to extract header information from '%s': %s", filename, rrd_get_error()); return NULL; } ts_info = calloc(1, sizeof(*ts_info)); if (! ts_info) { sdb_log(SDB_LOG_ERR, "Failed to allocate memory"); rrd_info_free(info); return NULL; } for (iter = info; iter != NULL; iter = iter->next) { size_t len, n, m; char *ds_name; char **tmp; /* Parse the DS name. The raw value is not exposed via the rrd_info * interface. */ n = strlen("ds["); if (strncmp(iter->key, "ds[", n)) continue; len = strlen(iter->key); m = strlen("].index"); if ((len < m) || strcmp(iter->key + len - m, "].index")) continue; ds_name = iter->key + n; len -= n; ds_name[len - m] = '\0'; /* Append the new datum. */ tmp = realloc(ts_info->data_names, (ts_info->data_names_len + 1) * sizeof(*ts_info->data_names)); if (! tmp) { sdb_log(SDB_LOG_ERR, "Failed to allocate memory"); sdb_timeseries_info_destroy(ts_info); rrd_info_free(info); return NULL; } ts_info->data_names = tmp; ts_info->data_names[ts_info->data_names_len] = strdup(ds_name); if (! ts_info->data_names[ts_info->data_names_len]) { sdb_log(SDB_LOG_ERR, "Failed to allocate memory"); sdb_timeseries_info_destroy(ts_info); rrd_info_free(info); return NULL; } ts_info->data_names_len++; } rrd_info_free(info); return ts_info; } /* sdb_rrd_describe */
static sdb_timeseries_t * sdb_rrd_fetch(const char *id, sdb_timeseries_opts_t *opts, sdb_object_t *user_data) { sdb_timeseries_t *ts; time_t start = (time_t)SDB_TIME_TO_SECS(opts->start); time_t end = (time_t)SDB_TIME_TO_SECS(opts->end); unsigned long step = 0; unsigned long ds_cnt = 0; unsigned long val_cnt = 0; char **ds_namv = NULL; rrd_value_t *data = NULL; if (user_data) { /* -> use RRDCacheD */ char *addr = SDB_OBJ_WRAPPER(user_data)->data; if (! rrdcached_connect(addr)) return NULL; #ifdef HAVE_RRD_CLIENT_H if (rrdc_flush(id)) { sdb_log(SDB_LOG_ERR, "Failed to flush '%s' through RRDCacheD: %s", id, rrd_get_error()); return NULL; } #endif } #define FREE_RRD_DATA() \ do { \ size_t i; \ for (i = 0; i < ds_cnt; ++i) \ rrd_freemem(ds_namv[i]); \ rrd_freemem(ds_namv); \ rrd_freemem(data); \ } while (0) /* limit to about 1000 data-points for now * TODO: make this configurable */ step = (end - start) / 1000; if (rrd_fetch_r(id, "AVERAGE", &start, &end, &step, &ds_cnt, &ds_namv, &data)) { char errbuf[1024]; sdb_strerror(errno, errbuf, sizeof(errbuf)); sdb_log(SDB_LOG_ERR, "Failed to fetch data from %s: %s", id, errbuf); return NULL; } val_cnt = (unsigned long)(end - start) / step; /* RRDtool does not support fetching specific data-sources, so we'll have * to filter the requested ones after fetching them all */ if (opts->data_names && opts->data_names_len) ts = sdb_timeseries_create(opts->data_names_len, (const char * const *)opts->data_names, val_cnt); else ts = sdb_timeseries_create(ds_cnt, (const char * const *)ds_namv, val_cnt); if (! ts) { char errbuf[1024]; sdb_strerror(errno, errbuf, sizeof(errbuf)); sdb_log(SDB_LOG_ERR, "Failed to allocate time-series object: %s", errbuf); FREE_RRD_DATA(); return NULL; } ts->start = SECS_TO_SDB_TIME(start + (time_t)step); ts->end = SECS_TO_SDB_TIME(end); if (copy_data(ts, data, (time_t)step, (size_t)ds_cnt, ds_namv) < 0) { FREE_RRD_DATA(); sdb_timeseries_destroy(ts); return NULL; } FREE_RRD_DATA(); return ts; } /* sdb_rrd_fetch */