diff --git a/include/types/server.h b/include/types/server.h index 4a0772685..568528976 100644 --- a/include/types/server.h +++ b/include/types/server.h @@ -345,6 +345,17 @@ struct server { struct sockaddr_storage socks4_addr; /* the address of the SOCKS4 Proxy, including the port */ }; + +/* Storage structure to load server-state lines from a flat file into + * an ebtree, for faster processing + */ +struct state_line { + char *line; + struct ebmb_node name_name; + /* WARNING don't put anything after name_name, it's used by the key */ +}; + + /* Descriptor for a "server" keyword. The ->parse() function returns 0 in case of * success, or a combination of ERR_* flags if an error is encountered. The * function pointer can be NULL if not implemented. The function also has an diff --git a/src/server.c b/src/server.c index 66fba992d..1a9dd1617 100644 --- a/src/server.c +++ b/src/server.c @@ -47,10 +47,14 @@ #include #include +#include + static void srv_update_status(struct server *s); static void srv_update_state(struct server *srv, int version, char **params); static int srv_apply_lastaddr(struct server *srv, int *err_code); static int srv_set_fqdn(struct server *srv, const char *fqdn, int dns_locked); +static void srv_state_parse_line(char *buf, const int version, char **params, char **srv_params); +static int srv_state_get_version(FILE *f); /* List head of all known server keywords */ static struct srv_kw_list srv_keywords = { @@ -69,6 +73,9 @@ struct dict server_name_dict = { .values = EB_ROOT_UNIQUE, }; +/* tree where global state_file is loaded */ +struct eb_root state_file = EB_ROOT; + int srv_downtime(const struct server *s) { if ((s->cur_state != SRV_ST_STOPPED) && s->last_change < now.tv_sec) // ignore negative time @@ -3363,6 +3370,130 @@ static void srv_update_state(struct server *srv, int version, char **params) } } + +/* + * read next line from file and return the server state version if one found. + * If no version is found, then 0 is returned + * Note that this should be the first read on + */ +static int srv_state_get_version(FILE *f) { + char buf[2]; + int ret; + + /* first character of first line of the file must contain the version of the export */ + if (fgets(buf, 2, f) == NULL) { + return 0; + } + + ret = atoi(buf); + if ((ret < SRV_STATE_FILE_VERSION_MIN) || + (ret > SRV_STATE_FILE_VERSION_MAX)) + return 0; + + return ret; +} + + +/* + * parses server state line stored in and supposedly in version . + * Set and accordingly. + * In case of error, params[0] is set to NULL. + */ +static void srv_state_parse_line(char *buf, const int version, char **params, char **srv_params) +{ + int buflen, arg, srv_arg; + char *cur, *end; + + buflen = strlen(buf); + cur = buf; + end = cur + buflen; + + /* we need at least one character */ + if (buflen == 0) { + params[0] = NULL; + return; + } + + /* ignore blank characters at the beginning of the line */ + while (isspace(*cur)) + ++cur; + + /* Ignore empty or commented lines */ + if (cur == end || *cur == '#') { + params[0] = NULL; + return; + } + + /* truncated lines */ + if (buf[buflen - 1] != '\n') { + //ha_warning("server-state file '%s': truncated line\n", filepath); + params[0] = NULL; + return; + } + + /* Removes trailing '\n' */ + buf[buflen - 1] = '\0'; + + /* we're now ready to move the line into *srv_params[] */ + params[0] = cur; + arg = 1; + srv_arg = 0; + while (*cur && arg < SRV_STATE_FILE_MAX_FIELDS) { + if (isspace(*cur)) { + *cur = '\0'; + ++cur; + while (isspace(*cur)) + ++cur; + switch (version) { + case 1: + /* + * srv_addr: params[4] => srv_params[0] + * srv_op_state: params[5] => srv_params[1] + * srv_admin_state: params[6] => srv_params[2] + * srv_uweight: params[7] => srv_params[3] + * srv_iweight: params[8] => srv_params[4] + * srv_last_time_change: params[9] => srv_params[5] + * srv_check_status: params[10] => srv_params[6] + * srv_check_result: params[11] => srv_params[7] + * srv_check_health: params[12] => srv_params[8] + * srv_check_state: params[13] => srv_params[9] + * srv_agent_state: params[14] => srv_params[10] + * bk_f_forced_id: params[15] => srv_params[11] + * srv_f_forced_id: params[16] => srv_params[12] + * srv_fqdn: params[17] => srv_params[13] + * srv_port: params[18] => srv_params[14] + * srvrecord: params[19] => srv_params[15] + */ + if (arg >= 4) { + srv_params[srv_arg] = cur; + ++srv_arg; + } + break; + } + + params[arg] = cur; + ++arg; + } + else { + ++cur; + } + } + + /* if line is incomplete line, then ignore it. + * otherwise, update useful flags */ + switch (version) { + case 1: + if (arg < SRV_STATE_FILE_NB_FIELDS_VERSION_1) { + params[0] = NULL; + return; + } + break; + } + + return; +} + + /* This function parses all the proxies and only take care of the backends (since we're looking for server) * For each proxy, it does the following: * - opens its server state file (either one or local one) @@ -3379,12 +3510,10 @@ static void srv_update_state(struct server *srv, int version, char **params) */ void apply_server_state(void) { - char *cur, *end; char mybuf[SRV_STATE_LINE_MAXLEN]; - int mybuflen; char *params[SRV_STATE_FILE_MAX_FIELDS] = {0}; char *srv_params[SRV_STATE_FILE_MAX_FIELDS] = {0}; - int arg, srv_arg, version; + int version, global_file_version; FILE *f; char *filepath; char globalfilepath[MAXPATHLEN + 1]; @@ -3392,7 +3521,13 @@ void apply_server_state(void) int len, fileopenerr, globalfilepathlen, localfilepathlen; struct proxy *curproxy, *bk; struct server *srv; + char *line; + char *bkname, *srvname; + struct state_line *st; + struct ebmb_node *node, *next_node; + + global_file_version = 0; globalfilepathlen = 0; /* create the globalfilepath variable */ if (global.server_state_file) { @@ -3440,7 +3575,57 @@ void apply_server_state(void) if (globalfilepathlen == 0) globalfilepath[0] = '\0'; - /* read servers state from local file */ + /* Load global server state in a tree */ + if (globalfilepathlen > 0) { + errno = 0; + f = fopen(globalfilepath, "r"); + if (errno) + ha_warning("Can't open global server state file '%s': %s\n", globalfilepath, strerror(errno)); + + global_file_version = srv_state_get_version(f); + if (global_file_version == 0) + goto out_load_server_state_in_tree; + + while (fgets(mybuf, SRV_STATE_LINE_MAXLEN, f)) { + line = NULL; + + line = strdup(mybuf); + if (line == NULL) + continue; + + srv_state_parse_line(mybuf, global_file_version, params, srv_params); + if (params[0] == NULL) + continue; + + /* bkname */ + bkname = params[1]; + /* srvname */ + srvname = params[3]; + + /* key */ + chunk_printf(&trash, "%s %s", bkname, srvname); + + /* store line in tree */ + st = calloc(1, sizeof(*st) + trash.size); + if (st == NULL) { + goto nextline; + } + memcpy(st->name_name.key, trash.area, trash.size); + ebst_insert(&state_file, &st->name_name); + + /* save line */ + st->line = line; + + continue; + + nextline: + /* free up memory in case of error during the processing of the line */ + free(line); + } + } + out_load_server_state_in_tree: + + /* parse all proxies and load states form tree (global file) or from local file */ for (curproxy = proxies_list; curproxy != NULL; curproxy = curproxy->next) { /* servers are only in backends */ if (!(curproxy->cap & PR_CAP_BE)) @@ -3515,156 +3700,125 @@ void apply_server_state(void) continue; } - /* preload global state file */ - errno = 0; - f = fopen(filepath, "r"); - if (errno && fileopenerr) - ha_warning("Can't open server state file '%s': %s\n", filepath, strerror(errno)); - if (!f) - continue; + /* when global file is used, we get data from the tree + * Note that in such case we don't check backend name neither uuid. + * Backend name can't be wrong since it's used as a key to retrieve the server state + * line from the tree. + */ + if (curproxy->load_server_state_from_file == PR_SRV_STATE_FILE_GLOBAL) { + struct server *srv = curproxy->srv; + while (srv) { + struct ebmb_node *node; + struct state_line *st; - mybuf[0] = '\0'; - mybuflen = 0; - version = 0; + chunk_printf(&trash, "%s %s", curproxy->id, srv->id); + node = ebst_lookup(&state_file, trash.area); + if (!node) + goto next; - /* first character of first line of the file must contain the version of the export */ - if (fgets(mybuf, SRV_STATE_LINE_MAXLEN, f) == NULL) { - ha_warning("Can't read first line of the server state file '%s'\n", filepath); - goto fileclose; + st = container_of(node, struct state_line, name_name); + memcpy(mybuf, st->line, strlen(st->line)); + mybuf[strlen(st->line)] = 0; + + srv_state_parse_line(mybuf, global_file_version, params, srv_params); + if (params[0] == NULL) + goto next; + + srv_update_state(srv, global_file_version, srv_params); + + next: + srv = srv->next; + } + + continue; /* next proxy in list */ } - - cur = mybuf; - version = atoi(cur); - if ((version < SRV_STATE_FILE_VERSION_MIN) || - (version > SRV_STATE_FILE_VERSION_MAX)) - goto fileclose; - - while (fgets(mybuf, SRV_STATE_LINE_MAXLEN, f)) { - int bk_f_forced_id = 0; - int check_id = 0; - int check_name = 0; - - mybuflen = strlen(mybuf); - cur = mybuf; - end = cur + mybuflen; - - bk = NULL; - srv = NULL; - - /* we need at least one character */ - if (mybuflen == 0) + else { + /* load 'local' state file */ + errno = 0; + f = fopen(filepath, "r"); + if (errno && fileopenerr) + ha_warning("Can't open server state file '%s': %s\n", filepath, strerror(errno)); + if (!f) continue; - /* ignore blank characters at the beginning of the line */ - while (isspace(*cur)) - ++cur; + mybuf[0] = '\0'; - /* Ignore empty or commented lines */ - if (cur == end || *cur == '#') - continue; - - /* truncated lines */ - if (mybuf[mybuflen - 1] != '\n') { - ha_warning("server-state file '%s': truncated line\n", filepath); - continue; + /* first character of first line of the file must contain the version of the export */ + version = srv_state_get_version(f); + if (version == 0) { + ha_warning("Can't get version of the server state file '%s'\n", filepath); + goto fileclose; } - /* Removes trailing '\n' */ - mybuf[mybuflen - 1] = '\0'; + while (fgets(mybuf, SRV_STATE_LINE_MAXLEN, f)) { + int bk_f_forced_id = 0; + int check_id = 0; + int check_name = 0; - /* we're now ready to move the line into *srv_params[] */ - params[0] = cur; - arg = 1; - srv_arg = 0; - while (*cur && arg < SRV_STATE_FILE_MAX_FIELDS) { - if (isspace(*cur)) { - *cur = '\0'; - ++cur; - while (isspace(*cur)) - ++cur; - switch (version) { - case 1: - /* - * srv_addr: params[4] => srv_params[0] - * srv_op_state: params[5] => srv_params[1] - * srv_admin_state: params[6] => srv_params[2] - * srv_uweight: params[7] => srv_params[3] - * srv_iweight: params[8] => srv_params[4] - * srv_last_time_change: params[9] => srv_params[5] - * srv_check_status: params[10] => srv_params[6] - * srv_check_result: params[11] => srv_params[7] - * srv_check_health: params[12] => srv_params[8] - * srv_check_state: params[13] => srv_params[9] - * srv_agent_state: params[14] => srv_params[10] - * bk_f_forced_id: params[15] => srv_params[11] - * srv_f_forced_id: params[16] => srv_params[12] - * srv_fqdn: params[17] => srv_params[13] - * srv_port: params[18] => srv_params[14] - * srvrecord: params[19] => srv_params[15] - */ - if (arg >= 4) { - srv_params[srv_arg] = cur; - ++srv_arg; - } - break; - } + srv_state_parse_line(mybuf, version, params, srv_params); - params[arg] = cur; - ++arg; - } - else { - ++cur; - } - } - - /* if line is incomplete line, then ignore it. - * otherwise, update useful flags */ - switch (version) { - case 1: - if (arg < SRV_STATE_FILE_NB_FIELDS_VERSION_1) - continue; - bk_f_forced_id = (atoi(params[15]) & PR_O_FORCED_ID); - check_id = (atoi(params[0]) == curproxy->uuid); - check_name = (strcmp(curproxy->id, params[1]) == 0); - break; - } - - bk = curproxy; - - /* if backend can't be found, let's continue */ - if (!check_id && !check_name) - continue; - else if (!check_id && check_name) { - ha_warning("backend ID mismatch: from server state file: '%s', from running config '%d'\n", params[0], bk->uuid); - send_log(bk, LOG_NOTICE, "backend ID mismatch: from server state file: '%s', from running config '%d'\n", params[0], bk->uuid); - } - else if (check_id && !check_name) { - ha_warning("backend name mismatch: from server state file: '%s', from running config '%s'\n", params[1], bk->id); - send_log(bk, LOG_NOTICE, "backend name mismatch: from server state file: '%s', from running config '%s'\n", params[1], bk->id); - /* if name doesn't match, we still want to update curproxy if the backend id - * was forced in previous the previous configuration */ - if (!bk_f_forced_id) + if (params[0] == NULL) { continue; + } + + /* if line is incomplete line, then ignore it. + * otherwise, update useful flags */ + switch (version) { + case 1: + bk_f_forced_id = (atoi(params[15]) & PR_O_FORCED_ID); + check_id = (atoi(params[0]) == curproxy->uuid); + check_name = (strcmp(curproxy->id, params[1]) == 0); + break; + } + + bk = curproxy; + + /* if backend can't be found, let's continue */ + if (!check_id && !check_name) + continue; + else if (!check_id && check_name) { + ha_warning("backend ID mismatch: from server state file: '%s', from running config '%d'\n", params[0], bk->uuid); + send_log(bk, LOG_NOTICE, "backend ID mismatch: from server state file: '%s', from running config '%d'\n", params[0], bk->uuid); + } + else if (check_id && !check_name) { + ha_warning("backend name mismatch: from server state file: '%s', from running config '%s'\n", params[1], bk->id); + send_log(bk, LOG_NOTICE, "backend name mismatch: from server state file: '%s', from running config '%s'\n", params[1], bk->id); + /* if name doesn't match, we still want to update curproxy if the backend id + * was forced in previous the previous configuration */ + if (!bk_f_forced_id) + continue; + } + + /* look for the server by its name: param[3] */ + srv = server_find_best_match(bk, params[3], 0, NULL); + + if (!srv) { + /* if no server found, then warning and continue with next line */ + ha_warning("can't find server '%s' in backend '%s'\n", + params[3], params[1]); + send_log(bk, LOG_NOTICE, "can't find server '%s' in backend '%s'\n", + params[3], params[1]); + continue; + } + + /* now we can proceed with server's state update */ + srv_update_state(srv, version, srv_params); } - - /* look for the server by its name: param[3] */ - srv = server_find_best_match(bk, params[3], 0, NULL); - - if (!srv) { - /* if no server found, then warning and continue with next line */ - ha_warning("can't find server '%s' in backend '%s'\n", - params[3], params[1]); - send_log(bk, LOG_NOTICE, "can't find server '%s' in backend '%s'\n", - params[3], params[1]); - continue; - } - - /* now we can proceed with server's state update */ - srv_update_state(srv, version, srv_params); } fileclose: fclose(f); } + + /* now free memory allocated for the tree */ + for (node = ebmb_first(&state_file), next_node = node ? ebmb_next(node) : NULL; + node; + node = next_node, next_node = node ? ebmb_next(node) : NULL) { + st = container_of(node, struct state_line, name_name); + ebmb_delete(&st->name_name); + free(st->line); + free(st); + } + } /*