Skip to content

Instantly share code, notes, and snippets.

@arslanm
Last active May 29, 2018 17:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arslanm/2f410740d8840d840bc610a6d957427c to your computer and use it in GitHub Desktop.
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 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\">"
"&nbsp;<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\">&nbsp;</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