MINOR: spoe/checks: Add support for SPOP health checks
A new "option spop-check" statement has been added to enable server health
checks based on SPOP HELLO handshake. SPOP is the protocol used by SPOE filters
to talk to servers.
diff --git a/contrib/spoa_example/spoa.c b/contrib/spoa_example/spoa.c
index 1bd822e..ce59c04 100644
--- a/contrib/spoa_example/spoa.c
+++ b/contrib/spoa_example/spoa.c
@@ -143,6 +143,7 @@
int status_code;
unsigned int stream_id;
unsigned int frame_id;
+ bool healthcheck;
int ip_score; /* -1 if unset, else between 0 and 100 */
};
@@ -580,6 +581,24 @@
return idx;
}
+/* Check healthcheck value. It returns -1 if an error occurred, the number of
+ * read bytes otherwise. */
+static int
+check_healthcheck(struct worker *w, int idx)
+{
+ int type;
+
+ /* Get the "healthcheck" value of HAProxy */
+ type = w->buf[idx++];
+ if ((type & SPOE_DATA_T_MASK) != SPOE_DATA_T_BOOL) {
+ w->status_code = SPOE_FRM_ERR_INVALID;
+ return -1;
+ }
+ w->healthcheck = ((type & SPOE_DATA_FL_TRUE) == SPOE_DATA_FL_TRUE);
+ return idx;
+}
+
+
/* Decode a HELLO frame received from HAProxy. It returns -1 if an error
* occurred, 0 if the frame must be skipped, otherwise the number of read
* bytes. */
@@ -627,6 +646,12 @@
goto error;
idx = i;
}
+ /* Check "healthcheck" K/V item "*/
+ else if (!memcmp(str, "healthcheck", sz)) {
+ if ((i = check_healthcheck(w, idx)) == -1)
+ goto error;
+ idx = i;
+ }
/* Skip "capabilities" K/V item for now */
else {
/* Silently ignore unknown item */
@@ -927,8 +952,8 @@
LOG("Failed to write Agent frame");
goto error;
}
- DEBUG("Hello handshake done: version=%s - max-frame-size=%u",
- SPOP_VERSION, w->size);
+ DEBUG("Hello handshake done: version=%s - max-frame-size=%u - healthcheck=%s",
+ SPOP_VERSION, w->size, (w->healthcheck ? "true" : "false"));
return 0;
error:
return -1;
@@ -993,7 +1018,8 @@
if (hello_handshake(csock, &w) < 0)
goto disconnect;
-
+ if (w.healthcheck == true)
+ goto close;
while (1) {
w.ip_score = -1;
if (notify_ack_roundtip(csock, &w) < 0)
diff --git a/doc/SPOE.txt b/doc/SPOE.txt
index 538bb26..fa0a533 100644
--- a/doc/SPOE.txt
+++ b/doc/SPOE.txt
@@ -493,12 +493,24 @@
HAPROXY AGENT SRV
| HAPROXY-HELLO |
+ | (healthcheck: false) |
| --------------------------> |
| |
| AGENT-HELLO |
| <-------------------------- |
| |
+ * Successful HELLO healthcheck:
+
+ HAPROXY AGENT SRV
+ | HAPROXY-HELLO |
+ | (healthcheck: true) |
+ | --------------------------> |
+ | |
+ | AGENT-HELLO + close() |
+ | <-------------------------- |
+ | |
+
* Error encountered by agent during the HELLO handshake:
@@ -581,6 +593,13 @@
This a comma-separated list of capabilities supported by HAProxy. Spaces
must be ignored, if any.
+Following optional items can be added in the KV-LIST:
+
+ * "healthcheck" <BOOLEAN>
+
+ If this item is set to TRUE, then the HAPROXY-HELLO frame is sent during a
+ SPOE health check. When set to FALSE, this item can be ignored.
+
To finish the HELLO handshake, the agent must return an AGENT-HELLO frame with
its supported SPOP version, the lower value between its maximum size allowed
for a frame and the HAProxy one and capabilities it supports. If an error
@@ -617,6 +636,10 @@
max-frame-size value), the HELLO handshake is successfully completed. Else,
HAProxy sends a HAPROXY-DISCONNECT frame with the corresponding error.
+If "healthcheck" item was set to TRUE in the HAPROXY-HELLO frame, the agent can
+safely close the connection without DISCONNECT frame. In all cases, HAProxy
+will close the connexion at the end of the health check.
+
3.2.6. Frame: NOTIFY
---------------------
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 3139650..dad0152 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1898,6 +1898,7 @@
option splice-auto (*) X X X X
option splice-request (*) X X X X
option splice-response (*) X X X X
+option spop-check - - - X
option srvtcpka (*) X - X X
option ssl-hello-chk X - X X
-- keyword -------------------------- defaults - frontend - listen -- backend -
@@ -6367,6 +6368,23 @@
"nosplice" and "maxpipes"
+option spop-check
+ Use SPOP health checks for server testing
+ May be used in sections : defaults | frontend | listen | backend
+ no | no | no | yes
+ Arguments : none
+
+ It is possible to test that the server correctly talks SPOP protocol instead
+ of just testing that it accepts the TCP connection. When this option is set,
+ a HELLO handshake is performed between HAProxy and the server, and the
+ response is analyzed to check no error is reported.
+
+ Example :
+ option spop-check
+
+ See also : "option httpchk"
+
+
option srvtcpka
no option srvtcpka
Enable or disable the sending of TCP keepalive packets on the server side
diff --git a/include/proto/checks.h b/include/proto/checks.h
index ecd4a5c..bf771ea 100644
--- a/include/proto/checks.h
+++ b/include/proto/checks.h
@@ -51,6 +51,11 @@
void send_email_alert(struct server *s, int priority, const char *format, ...)
__attribute__ ((format(printf, 3, 4)));
int srv_check_healthcheck_port(struct check *chk);
+
+/* Declared here, but the definitions are in flt_spoe.c */
+int prepare_spoe_healthcheck_request(char **req, int *len);
+int handle_spoe_healthcheck_response(char *frame, size_t size, char *err, int errlen);
+
#endif /* _PROTO_CHECKS_H */
/*
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 80d6a64..27aa157 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -173,7 +173,8 @@
#define PR_O2_LB_AGENT_CHK 0x80000000 /* use a TCP connection to obtain a metric of server health */
#define PR_O2_TCPCHK_CHK 0x90000000 /* use TCPCHK check for server health */
#define PR_O2_EXT_CHK 0xA0000000 /* use external command for server health */
-/* unused: 0xB0000000 to 0xF000000, reserved for health checks */
+#define PR_O2_SPOP_CHK 0xB0000000 /* use SPOP for server health */
+/* unused: 0xC0000000 to 0xF000000, reserved for health checks */
#define PR_O2_CHK_ANY 0xF0000000 /* Mask to cover any check */
/* end of proxy->options2 */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index acd570d..7b05727 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -5118,6 +5118,34 @@
if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
goto out;
}
+ else if (!strcmp(args[1], "spop-check")) {
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n",
+ file, linenum, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (curproxy->cap & PR_CAP_FE) {
+ Alert("parsing [%s:%d] : '%s %s' not allowed in 'frontend' and 'listen' sections.\n",
+ file, linenum, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ /* use SPOE request to check servers' health */
+ free(curproxy->check_req);
+ curproxy->check_req = NULL;
+ curproxy->options2 &= ~PR_O2_CHK_ANY;
+ curproxy->options2 |= PR_O2_SPOP_CHK;
+
+ if (prepare_spoe_healthcheck_request(&curproxy->check_req, &curproxy->check_len)) {
+ Alert("parsing [%s:%d] : failed to prepare SPOP healthcheck request.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
+ goto out;
+ }
else if (!strcmp(args[1], "tcp-check")) {
/* use raw TCPCHK send/expect to check servers' health */
if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
diff --git a/src/checks.c b/src/checks.c
index 65d0037..84a0f58 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1317,6 +1317,26 @@
}
break;
+ case PR_O2_SPOP_CHK: {
+ unsigned int framesz;
+ char err[HCHK_DESC_LEN];
+
+ if (!done && check->bi->i < 4)
+ goto wait_more_data;
+
+ memcpy(&framesz, check->bi->data, 4);
+ framesz = ntohl(framesz);
+
+ if (!done && check->bi->i < (4+framesz))
+ goto wait_more_data;
+
+ if (!handle_spoe_healthcheck_response(check->bi->data+4, framesz, err, HCHK_DESC_LEN-1))
+ set_server_check_status(check, HCHK_STATUS_L7OKD, "SPOA server is ok");
+ else
+ set_server_check_status(check, HCHK_STATUS_L7STS, err);
+ break;
+ }
+
default:
/* for other checks (eg: pure TCP), delegate to the main task */
break;
diff --git a/src/flt_spoe.c b/src/flt_spoe.c
index 1ebdbda..12e589e 100644
--- a/src/flt_spoe.c
+++ b/src/flt_spoe.c
@@ -414,6 +414,7 @@
#define VERSION_KEY "version"
#define MAX_FRAME_SIZE_KEY "max-frame-size"
#define CAPABILITIES_KEY "capabilities"
+#define HEALTHCHECK_KEY "healthcheck"
#define STATUS_CODE_KEY "status-code"
#define MSG_KEY "message"
@@ -1075,6 +1076,70 @@
return idx;
}
+/* This function is used in cfgparse.c and declared in proto/checks.h. It
+ * prepare the request to send to agents during a healthcheck. It returns 0 on
+ * success and -1 if an error occurred. */
+int
+prepare_spoe_healthcheck_request(char **req, int *len)
+{
+ struct appctx a;
+ char *frame, buf[global.tune.bufsize];
+ unsigned int framesz;
+ int idx;
+
+ memset(&a, 0, sizeof(a));
+ memset(buf, 0, sizeof(buf));
+ APPCTX_SPOE(&a).max_frame_size = global.tune.bufsize;
+
+ frame = buf+4;
+ idx = prepare_spoe_hahello_frame(&a, frame, global.tune.bufsize-4);
+ if (idx <= 0)
+ return -1;
+ if (idx + SLEN(HEALTHCHECK_KEY) + 1 > global.tune.bufsize-4)
+ return -1;
+
+ /* "healthcheck" K/V item */
+ idx += encode_spoe_string(HEALTHCHECK_KEY, SLEN(HEALTHCHECK_KEY), frame+idx);
+ frame[idx++] = (SPOE_DATA_T_BOOL | SPOE_DATA_FL_TRUE);
+
+ framesz = htonl(idx);
+ memcpy(buf, (char *)&framesz, 4);
+
+ if ((*req = malloc(idx+4)) == NULL)
+ return -1;
+ memcpy(*req, buf, idx+4);
+ *len = idx+4;
+ return 0;
+}
+
+/* This function is used in checks.c and declared in proto/checks.h. It decode
+ * the response received from an agent during a healthcheck. It returns 0 on
+ * success and -1 if an error occurred. */
+int
+handle_spoe_healthcheck_response(char *frame, size_t size, char *err, int errlen)
+{
+ struct appctx a;
+ int r;
+
+ memset(&a, 0, sizeof(a));
+ APPCTX_SPOE(&a).max_frame_size = global.tune.bufsize;
+
+ if (handle_spoe_agentdiscon_frame(&a, frame, size) != 0)
+ goto error;
+ if ((r = handle_spoe_agenthello_frame(&a, frame, size)) <= 0) {
+ if (r == 0)
+ spoe_status_code = SPOE_FRM_ERR_INVALID;
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ if (spoe_status_code >= SPOE_FRM_ERRS)
+ spoe_status_code = SPOE_FRM_ERR_UNKNOWN;
+ strncpy(err, spoe_frm_err_reasons[spoe_status_code], errlen);
+ return -1;
+}
/********************************************************************
* Functions that manage the SPOE applet