Last active
May 29, 2018 17:31
-
-
Save arslanm/2f410740d8840d840bc610a6d957427c to your computer and use it in GitHub Desktop.
HAProxy patch against 1.6.7 to control the backup flag of a server using state file, and have the ability to save global server-state-file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This patch lets you: | |
# - put server/backend in backup mode, or active mode; "backup" parameter for server isn't needed in the configuration file. | |
# - save global server state to 'server-state-file' (absolute path required) | |
# see the UI here: http://i.imgur.com/n2O5V6P.png | |
# Instructions | |
$ wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.7.tar.gz | |
$ tar xf haproxy-1.6.7.tar.gz | |
$ cd haproxy-1.6.7 | |
$ cat below_patch | patch -p1 | |
$ make USE_PCRE=1 DEBUG="" ARCH=$(uname -m) TARGET=linux26 | |
$ sudo make install | |
/etc/haproxy/haproxy.cfg: | |
global | |
... | |
user haproxy | |
group haproxy | |
stats socket /etc/haproxy/haproxy.sock level admin | |
server-state-file /etc/haproxy/haproxy.state | |
... | |
defaults | |
... | |
load-server-state-from-file global | |
... | |
# make sure the state file is there and it's owned by user haproxy | |
$ sudo touch /etc/haproxy/haproxy.state | |
$ sudo chown haproxy.haproxy /etc/haproxy/haproxy.state | |
# put myserver in mybackend into backup mode | |
$ echo "set server mybackend/myserver state backup" | socat unix-connect:/etc/haproxy/haproxy.sock stdio | |
# or make it active again | |
$ echo "set server mybackend/myserver state active" | socat unix-connect:/etc/haproxy/haproxy.sock stdio | |
# save haproxy state | |
$ echo "show servers state" | socat unix-connect:/etc/haproxy/haproxy.sock stdio > /etc/haproxy/haproxy.state | |
# -----------------------------------------------------------------------------------------------------------------# | |
diff -urN a/doc/management.txt b/doc/management.txt | |
--- a/doc/management.txt 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/doc/management.txt 2016-07-26 23:04:28.323646629 +0000 | |
@@ -1586,6 +1586,7 @@ | |
configuration. | |
srv_f_forced_id: Flag to know if the server's ID is forced by | |
configuration. | |
+ srv_is_backup: Flag to know if the server's in backup mode | |
show sess | |
Dump all known sessions. Avoid doing this on slow connections as this can | |
diff -urN a/include/types/proto_http.h b/include/types/proto_http.h | |
--- a/include/types/proto_http.h 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/include/types/proto_http.h 2016-07-29 00:11:51.373705211 +0000 | |
@@ -263,6 +263,8 @@ | |
STAT_STATUS_NONE, /* nothing happened (no action chosen or servers state didn't change) */ | |
STAT_STATUS_PART, /* the action is partially successful */ | |
STAT_STATUS_UNKN, /* an unknown error occured, shouldn't happen */ | |
+ STAT_STATUS_SAVE_OK, /* global save-state save successful */ | |
+ STAT_STATUS_SAVE_ERR, /* global save-state save failed */ | |
STAT_STATUS_SIZE | |
}; | |
diff -urN a/include/types/server.h b/include/types/server.h | |
--- a/include/types/server.h 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/include/types/server.h 2016-07-26 23:04:28.323646629 +0000 | |
@@ -86,7 +86,7 @@ | |
#define SRV_STATE_FILE_VERSION 1 | |
#define SRV_STATE_FILE_VERSION_MIN 1 | |
#define SRV_STATE_FILE_VERSION_MAX 1 | |
-#define SRV_STATE_FILE_FIELD_NAMES "be_id be_name srv_id srv_name srv_addr srv_op_state srv_admin_state srv_uweight srv_iweight srv_time_since_last_change srv_check_status srv_check_result srv_check_health srv_check_state srv_agent_state bk_f_forced_id srv_f_forced_id" | |
+#define SRV_STATE_FILE_FIELD_NAMES "be_id be_name srv_id srv_name srv_addr srv_op_state srv_admin_state srv_uweight srv_iweight srv_time_since_last_change srv_check_status srv_check_result srv_check_health srv_check_state srv_agent_state bk_f_forced_id srv_f_forced_id srv_is_backup" | |
#define SRV_STATE_FILE_MAX_FIELDS 18 | |
#define SRV_STATE_FILE_NB_FIELDS_VERSION_1 18 | |
#define SRV_STATE_LINE_MAXLEN 512 | |
diff -urN a/src/dumpstats.c b/src/dumpstats.c | |
--- a/src/dumpstats.c 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/src/dumpstats.c 2016-07-29 18:32:56.605201609 +0000 | |
@@ -23,6 +23,7 @@ | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
+#include <sys/file.h> | |
#include <common/cfgparse.h> | |
#include <common/compat.h> | |
@@ -117,6 +118,10 @@ | |
ST_ADM_ACTION_ARUNN, | |
ST_ADM_ACTION_ADOWN, | |
+ /* backup/active states */ | |
+ ST_ADM_ACTION_BACKUP, | |
+ ST_ADM_ACTION_ACTIVE, | |
+ | |
/* set admin state */ | |
ST_ADM_ACTION_READY, | |
ST_ADM_ACTION_DRAIN, | |
@@ -1529,6 +1534,20 @@ | |
srv_adm_set_drain(sv); | |
else if (strcmp(args[4], "maint") == 0) | |
srv_adm_set_maint(sv); | |
+ else if (strcmp(args[4], "backup") == 0) { | |
+ if (!(sv->flags & SRV_F_BACKUP)) { | |
+ sv->flags |= SRV_F_BACKUP; | |
+ sv->proxy->srv_act --; | |
+ sv->proxy->srv_bck ++; | |
+ } | |
+ } | |
+ else if (strcmp(args[4], "active") == 0) { | |
+ if (sv->flags & SRV_F_BACKUP) { | |
+ sv->flags &= ~SRV_F_BACKUP; | |
+ sv->proxy->srv_act ++; | |
+ sv->proxy->srv_bck --; | |
+ } | |
+ } | |
else { | |
appctx->ctx.cli.msg = "'set server <srv> state' expects 'ready', 'drain' and 'maint'.\n"; | |
appctx->st0 = STAT_CLI_PRINT; | |
@@ -2749,6 +2768,7 @@ | |
char srv_addr[INET6_ADDRSTRLEN + 1]; | |
time_t srv_time_since_last_change; | |
int bk_f_forced_id, srv_f_forced_id; | |
+ int srv_is_backup; | |
/* we don't want to report any state if the backend is not enabled on this process */ | |
@@ -2764,6 +2784,7 @@ | |
srv_time_since_last_change = 0; | |
bk_f_forced_id = 0; | |
srv_f_forced_id = 0; | |
+ srv_is_backup = 0; | |
switch (srv->addr.ss_family) { | |
case AF_INET: | |
@@ -2778,19 +2799,20 @@ | |
srv_time_since_last_change = now.tv_sec - srv->last_change; | |
bk_f_forced_id = appctx->ctx.server_state.px->options & PR_O_FORCED_ID ? 1 : 0; | |
srv_f_forced_id = srv->flags & SRV_F_FORCED_ID ? 1 : 0; | |
+ srv_is_backup = srv->flags & SRV_F_BACKUP ? 1 : 0; | |
chunk_appendf(buf, | |
"%d %s " | |
"%d %s %s " | |
"%d %d %d %d %ld " | |
"%d %d %d %d %d " | |
- "%d %d" | |
+ "%d %d %d" | |
"\n", | |
appctx->ctx.server_state.px->uuid, appctx->ctx.server_state.px->id, | |
srv->puid, srv->id, srv_addr, | |
srv->state, srv->admin, srv->uweight, srv->iweight, (long int)srv_time_since_last_change, | |
srv->check.status, srv->check.result, srv->check.health, srv->check.state, srv->agent.state, | |
- bk_f_forced_id, srv_f_forced_id); | |
+ bk_f_forced_id, srv_f_forced_id, srv_is_backup); | |
if (bi_putchk(si_ic(si), &trash) == -1) { | |
si_applet_cant_put(si); | |
return 0; | |
@@ -2885,6 +2907,67 @@ | |
return 1; | |
} | |
+/* Parses backend list and dumps the state to global server state file | |
+ */ | |
+static int stats_save_state_to_file(struct stream_interface *si) | |
+{ | |
+ FILE *fp; | |
+ struct appctx *appctx = __objt_appctx(si->end); | |
+ char globalfilepath[MAXPATHLEN + 1]; | |
+ int len, errno, globalfilepathlen; | |
+ extern struct proxy *proxy; | |
+ struct proxy *curproxy; | |
+ struct chunk *mybuf; | |
+ | |
+ if (!global.server_state_file) { | |
+ return 0; | |
+ } | |
+ | |
+ if (global.server_state_file[0] == '/') { | |
+ len = strlen(global.server_state_file); | |
+ if (len > MAXPATHLEN) { | |
+ return 0; | |
+ } | |
+ memcpy(globalfilepath, global.server_state_file, len); | |
+ globalfilepath[len] = '\0'; | |
+ globalfilepathlen = len; | |
+ } else { | |
+ return 0; | |
+ } | |
+ | |
+ fp = fopen(globalfilepath, "w"); | |
+ if(errno || !fp) { | |
+ return 0; | |
+ } | |
+ | |
+ if(flock(fileno(fp), LOCK_EX) == -1) { | |
+ fclose(fp); | |
+ return 0; | |
+ } | |
+ | |
+ mybuf = get_trash_chunk(); | |
+ chunk_reset(mybuf); | |
+ | |
+ chunk_appendf(mybuf, "%d\n# %s\n", SRV_STATE_FILE_VERSION, SRV_STATE_FILE_FIELD_NAMES); | |
+ | |
+ appctx->ctx.server_state.px = proxy; | |
+ | |
+ for (; appctx->ctx.server_state.px != NULL; appctx->ctx.server_state.px = curproxy->next) { | |
+ curproxy = appctx->ctx.server_state.px; | |
+ if (curproxy->cap & PR_CAP_BE) { | |
+ if (!dump_servers_state(si, mybuf)) | |
+ return 0; | |
+ } | |
+ } | |
+ | |
+ fprintf(fp, "%s", mybuf->str); | |
+ | |
+ flock(fileno(fp), LOCK_UN); | |
+ fclose(fp); | |
+ | |
+ return 1; | |
+} | |
+ | |
/* This function dumps memory usage information onto the stream interface's | |
* read buffer. It returns 0 as long as it does not complete, non-zero upon | |
* completion. No state is used. | |
@@ -4081,19 +4164,31 @@ | |
"Choose the action to perform on the checked servers : " | |
"<select name=action>" | |
"<option value=\"\"></option>" | |
- "<option value=\"ready\">Set state to READY</option>" | |
- "<option value=\"drain\">Set state to DRAIN</option>" | |
- "<option value=\"maint\">Set state to MAINT</option>" | |
- "<option value=\"dhlth\">Health: disable checks</option>" | |
- "<option value=\"ehlth\">Health: enable checks</option>" | |
- "<option value=\"hrunn\">Health: force UP</option>" | |
- "<option value=\"hnolb\">Health: force NOLB</option>" | |
- "<option value=\"hdown\">Health: force DOWN</option>" | |
- "<option value=\"dagent\">Agent: disable checks</option>" | |
- "<option value=\"eagent\">Agent: enable checks</option>" | |
- "<option value=\"arunn\">Agent: force UP</option>" | |
- "<option value=\"adown\">Agent: force DOWN</option>" | |
+ "<optgroup label=\"OP states\">" | |
+ "<option value=\"ready\">Set OP state to READY</option>" | |
+ "<option value=\"drain\">Set OP state to DRAIN</option>" | |
+ "<option value=\"maint\">Set OP state to MAINT</option>" | |
+ "</optgroup>" | |
+ "<optgroup label=\"BACKUP states\">" | |
+ "<option value=\"backup\">Set BACKUP state to ON</option>" | |
+ "<option value=\"active\">Set BACKUP state to OFF</option>" | |
+ "</optgroup>" | |
+ "<optgroup label=\"HEALTH actions\">" | |
+ "<option value=\"dhlth\">HEALTH: disable checks</option>" | |
+ "<option value=\"ehlth\">HEALTH: enable checks</option>" | |
+ "<option value=\"hrunn\">HEALTH: force UP</option>" | |
+ "<option value=\"hnolb\">HEALTH: force NOLB</option>" | |
+ "<option value=\"hdown\">HEALTH: force DOWN</option>" | |
+ "</optgroup>" | |
+ "<optgroup label=\"AGENT actions\">" | |
+ "<option value=\"dagent\">AGENT: disable checks</option>" | |
+ "<option value=\"eagent\">AGENT: enable checks</option>" | |
+ "<option value=\"arunn\">AGENT: force UP</option>" | |
+ "<option value=\"adown\">AGENT: force DOWN</option>" | |
+ "</optgroup>" | |
+ "<optgroup label=\"OTHER actions\">" | |
"<option value=\"shutdown\">Kill Sessions</option>" | |
+ "</optgroup>" | |
"</select>" | |
"<input type=\"hidden\" name=\"b\" value=\"#%d\">" | |
" <input type=\"submit\" value=\"Apply\">" | |
@@ -4492,7 +4587,29 @@ | |
"<b>system limits:</b> memmax = %s%s; ulimit-n = %d<br>\n" | |
"<b>maxsock = </b> %d; <b>maxconn = </b> %d; <b>maxpipes = </b> %d<br>\n" | |
"current conns = %d; current pipes = %d/%d; conn rate = %d/sec<br>\n" | |
- "Running tasks: %d/%d; idle = %d %%<br>\n" | |
+ "Running tasks: %d/%d; idle = %d %%<br>\n", | |
+ (uri->flags & ST_HIDEVER) ? "" : (STATS_VERSION_STRING), | |
+ pid, (uri->flags & ST_SHNODE) ? " on " : "", | |
+ (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : "", | |
+ (uri->flags & ST_SHDESC) ? ": " : "", | |
+ (uri->flags & ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "", | |
+ pid, relative_pid, global.nbproc, | |
+ up / 86400, (up % 86400) / 3600, | |
+ (up % 3600) / 60, (up % 60), | |
+ global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", | |
+ global.rlimit_memmax ? " MB" : "", | |
+ global.rlimit_nofile, | |
+ global.maxsock, global.maxconn, global.maxpipes, | |
+ actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec), | |
+ run_queue_cur, nb_tasks_cur, idle_pct | |
+ ); | |
+ | |
+ if(global.server_state_file) { | |
+ chunk_appendf(&trash, | |
+ "<form method=\"POST\"><input type=\"hidden\" name=\"w\" value=\"y\"><input type=\"submit\" value=\"Save global server state to %s\"></form><br>\n", global.server_state_file); | |
+ } | |
+ | |
+ chunk_appendf(&trash, | |
"</td><td align=\"center\" nowrap>\n" | |
"<table class=\"lgd\"><tr>\n" | |
"<td class=\"active_up\"> </td><td class=\"noborder\">active UP </td>" | |
@@ -4514,23 +4631,8 @@ | |
"Note: \"NOLB\"/\"DRAIN\" = UP with load-balancing disabled." | |
"</td>" | |
"<td align=\"left\" valign=\"top\" nowrap width=\"1%%\">" | |
- "<b>Display option:</b><ul style=\"margin-top: 0.25em;\">" | |
- "", | |
- (uri->flags & ST_HIDEVER) ? "" : (STATS_VERSION_STRING), | |
- pid, (uri->flags & ST_SHNODE) ? " on " : "", | |
- (uri->flags & ST_SHNODE) ? (uri->node ? uri->node : global.node) : "", | |
- (uri->flags & ST_SHDESC) ? ": " : "", | |
- (uri->flags & ST_SHDESC) ? (uri->desc ? uri->desc : global.desc) : "", | |
- pid, relative_pid, global.nbproc, | |
- up / 86400, (up % 86400) / 3600, | |
- (up % 3600) / 60, (up % 60), | |
- global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", | |
- global.rlimit_memmax ? " MB" : "", | |
- global.rlimit_nofile, | |
- global.maxsock, global.maxconn, global.maxpipes, | |
- actconn, pipes_used, pipes_used+pipes_free, read_freq_ctr(&global.conn_per_sec), | |
- run_queue_cur, nb_tasks_cur, idle_pct | |
- ); | |
+ "<b>Options:</b><ul style=\"margin-top: 0.25em;\">"); | |
+ | |
/* scope_txt = search query, appctx->ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */ | |
memcpy(scope_txt, bo_ptr(si_ob(si)) + appctx->ctx.stats.scope_str, appctx->ctx.stats.scope_len); | |
@@ -4676,6 +4778,26 @@ | |
(appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "", | |
scope_txt); | |
break; | |
+ case STAT_STATUS_SAVE_OK: | |
+ chunk_appendf(&trash, | |
+ "<p><div class=active_up>" | |
+ "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> " | |
+ "Global server state saved to '%s'" | |
+ "</div>\n", uri->uri_prefix, | |
+ (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "", | |
+ (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "", | |
+ scope_txt, global.server_state_file); | |
+ break; | |
+ case STAT_STATUS_SAVE_ERR: | |
+ chunk_appendf(&trash, | |
+ "<p><div class=active_down>" | |
+ "<a class=lfsb href=\"%s%s%s%s\" title=\"Remove this message\">[X]</a> " | |
+ "Global server state could not be saved to '%s'" | |
+ "</div>\n", uri->uri_prefix, | |
+ (appctx->ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "", | |
+ (appctx->ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "", | |
+ scope_txt, global.server_state_file); | |
+ break; | |
default: | |
chunk_appendf(&trash, | |
"<p><div class=active_no_check>" | |
@@ -4873,8 +4995,17 @@ | |
if (url_decode(key) < 0 || url_decode(value) < 0) | |
break; | |
+ /* check if we're asked to save the state */ | |
+ if (strcmp(key, "w") == 0) { | |
+ if (stats_save_state_to_file(si)) { | |
+ appctx->ctx.stats.st_code = STAT_STATUS_SAVE_OK; | |
+ } else { | |
+ appctx->ctx.stats.st_code = STAT_STATUS_SAVE_ERR; | |
+ } | |
+ goto out; | |
+ } | |
/* Now we can check the key to see what to do */ | |
- if (!px && (strcmp(key, "b") == 0)) { | |
+ else if (!px && (strcmp(key, "b") == 0)) { | |
if ((px = proxy_be_by_name(value)) == NULL) { | |
/* the backend name is unknown or ambiguous (duplicate names) */ | |
appctx->ctx.stats.st_code = STAT_STATUS_ERRP; | |
@@ -4891,6 +5022,12 @@ | |
else if (strcmp(value, "maint") == 0) { | |
action = ST_ADM_ACTION_MAINT; | |
} | |
+ else if (strcmp(value, "backup") == 0) { | |
+ action = ST_ADM_ACTION_BACKUP; | |
+ } | |
+ else if (strcmp(value, "active") == 0) { | |
+ action = ST_ADM_ACTION_ACTIVE; | |
+ } | |
else if (strcmp(value, "shutdown") == 0) { | |
action = ST_ADM_ACTION_SHUTDOWN; | |
} | |
@@ -5064,6 +5201,24 @@ | |
altered_servers++; | |
total_servers++; | |
break; | |
+ case ST_ADM_ACTION_BACKUP: | |
+ if (!(sv->flags & SRV_F_BACKUP)) { | |
+ sv->flags |= SRV_F_BACKUP; | |
+ px->srv_act --; | |
+ px->srv_bck ++; | |
+ altered_servers ++; | |
+ total_servers ++; | |
+ } | |
+ break; | |
+ case ST_ADM_ACTION_ACTIVE: | |
+ if (sv->flags & SRV_F_BACKUP) { | |
+ sv->flags &= ~SRV_F_BACKUP; | |
+ px->srv_act ++; | |
+ px->srv_bck --; | |
+ altered_servers ++; | |
+ total_servers ++; | |
+ } | |
+ break; | |
case ST_ADM_ACTION_SHUTDOWN: | |
if (px->state != PR_STSTOPPED) { | |
struct stream *sess, *sess_bck; | |
diff -urN a/src/proto_http.c b/src/proto_http.c | |
--- a/src/proto_http.c 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/src/proto_http.c 2016-07-29 17:09:13.824000318 +0000 | |
@@ -233,6 +233,8 @@ | |
[STAT_STATUS_NONE] = "NONE", | |
[STAT_STATUS_PART] = "PART", | |
[STAT_STATUS_UNKN] = "UNKN", | |
+ [STAT_STATUS_SAVE_OK] = "SVOK", | |
+ [STAT_STATUS_SAVE_ERR] = "SVER" | |
}; | |
diff -urN a/src/server.c b/src/server.c | |
--- a/src/server.c 2016-07-13 17:57:01.000000000 +0000 | |
+++ b/src/server.c 2016-07-26 23:04:28.325646629 +0000 | |
@@ -1918,6 +1918,7 @@ | |
int srv_check_state, srv_agent_state; | |
int bk_f_forced_id; | |
int srv_f_forced_id; | |
+ int srv_is_backup; | |
msg = get_trash_chunk(); | |
switch (version) { | |
@@ -1937,6 +1938,7 @@ | |
* srv_agent_state: params[10] | |
* bk_f_forced_id: params[11] | |
* srv_f_forced_id: params[12] | |
+ * srv_is_backup: params[13] | |
*/ | |
/* validating srv_op_state */ | |
@@ -2045,6 +2047,13 @@ | |
if (p == params[12] || errno == EINVAL || errno == ERANGE || !((srv_f_forced_id == 0) || (srv_f_forced_id == 1))) | |
chunk_appendf(msg, ", invalid srv_f_forced_id value '%s'", params[12]); | |
+ /* validating srv_is_backup */ | |
+ p = NULL; | |
+ errno = 0; | |
+ srv_is_backup = strtol(params[13], &p, 10); | |
+ if (p == params[13] || errno == EINVAL || errno == ERANGE || !((srv_is_backup == 0) || (srv_is_backup == 1))) | |
+ chunk_appendf(msg, ", invalid srv_is_backup value '%s'", params[13]); | |
+ | |
/* don't apply anything if one error has been detected */ | |
if (msg->len) | |
@@ -2077,6 +2086,20 @@ | |
* state is different from new configuration state | |
*/ | |
/* configuration has changed */ | |
+ if (srv_is_backup) { | |
+ if (!(srv->flags & SRV_F_BACKUP)) { | |
+ srv->flags |= SRV_F_BACKUP; | |
+ srv->proxy->srv_act --; | |
+ srv->proxy->srv_bck ++; | |
+ } | |
+ } | |
+ else { | |
+ if (srv->flags & SRV_F_BACKUP) { | |
+ srv->flags &= ~SRV_F_BACKUP; | |
+ srv->proxy->srv_act ++; | |
+ srv->proxy->srv_bck --; | |
+ } | |
+ } | |
if ((srv_admin_state & SRV_ADMF_CMAINT) != (srv->admin & SRV_ADMF_CMAINT)) { | |
if (srv->admin & SRV_ADMF_CMAINT) | |
srv_adm_set_maint(srv); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment