]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Implement postgres check using tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 2 Apr 2020 09:44:39 +0000 (11:44 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:38 +0000 (09:39 +0200)
A shared tcp-check ruleset is now created to support postgres check. This way no
extra memory is used if several backends use a pgsql check.

The following sequence is used :

    tcp-check connect default linger

    tcp-check send-binary PGSQL_REQ log-format

    tcp-check expect !rstring "^E" min-recv 5 \
        error-status "L7RSP" on-error "%[check.payload(6,0)]"

    tcp-check expect rbinary "^520000000800000000 min-recv "9" \
        error-status "L7STS" \
        on-success "PostgreSQL server is ok" \
        on-error "PostgreSQL unknown error"

The log-format hexa string PGSQL_REQ depends on 2 preset variables, the packet
length (check.plen) and the username (check.username).

include/proto/checks.h
include/types/checks.h
include/types/proxy.h
src/cfgparse-listen.c
src/checks.c

index b3ea9175bfe315d8082fbd224a6994f47c585d99..bb275c8de205b7794971335b1ab87232605400cd 100644 (file)
@@ -74,7 +74,9 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s
 int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
                                  const char *file, int line);
 int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
-                           const char *file, int line);
+                       const char *file, int line);
+int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                               const char *file, int line);
 
 #endif /* _PROTO_CHECKS_H */
 
index 541666c664b6da0c986a5c7fe65bbe0e788663e0..9b34cbe45505b65e9ba4a30257ca0d4f34797759 100644 (file)
@@ -312,6 +312,7 @@ struct tcpcheck_rule {
 #define TCPCHK_RULES_SHARED      0x00000001 /* Set for shared list of tcp-check rules */
 #define TCPCHK_RULES_DEF         0x00000002 /* Ruleset inherited from the default section */
 
+#define TCPCHK_RULES_PGSQL_CHK   0x00000010
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
 #define TCPCHK_RULES_SMTP_CHK    0x00000030
 #define TCPCHK_RULES_SSL3_CHK    0x00000070
index 0f4301d272bef626a606753e97b4f22694652c36..be5b5c9d20e5ac1b0ab989e317ce842bcc1f5f84 100644 (file)
@@ -169,9 +169,7 @@ enum PR_SRV_STATE_FILE {
 
 /* server health checks */
 #define PR_O2_CHK_NONE  0x00000000      /* no L7 health checks configured (TCP by default) */
-#define PR_O2_PGSQL_CHK 0x10000000      /* use PGSQL check for server health */
-/* unused: 0x20000000 */
-/* unused  0x30000000 */
+/* unused: 0x10000000..0x30000000 */
 #define PR_O2_HTTP_CHK  0x40000000      /* use HTTP 'OPTIONS' method to check server health */
 #define PR_O2_MYSQL_CHK 0x50000000      /* use MYSQL check for server health */
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
index e85cde2d51f40160804dc03eeb83f0cdd3a4535f..e0b1bc30d658ae847a1b869823737454fd91b5a4 100644 (file)
@@ -2407,63 +2407,8 @@ stats_error_parsing:
                                goto out;
                }
                else if (!strcmp(args[1], "pgsql-check")) {
-                       /* use PostgreSQL request to check servers' health */
-                       if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
-                               err_code |= ERR_WARN;
-
-                       free(curproxy->check_req);
-                       curproxy->check_req = NULL;
-                       curproxy->options2 &= ~PR_O2_CHK_ANY;
-                       curproxy->options2 |= PR_O2_PGSQL_CHK;
-
-                       if (*(args[2])) {
-                               int cur_arg = 2;
-
-                               while (*(args[cur_arg])) {
-                                       if (strcmp(args[cur_arg], "user") == 0) {
-                                               char * packet;
-                                               uint32_t packet_len;
-                                               uint32_t pv;
-
-                                               /* suboption header - needs additional argument for it */
-                                               if (*(args[cur_arg+1]) == 0) {
-                                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
-                                                                file, linenum, args[0], args[1], args[cur_arg]);
-                                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                                       goto out;
-                                               }
-
-                                               /* uint32_t + uint32_t + strlen("user")+1 + strlen(username)+1 + 1 */
-                                               packet_len = 4 + 4 + 5 + strlen(args[cur_arg + 1])+1 +1;
-                                               pv = htonl(0x30000); /* protocol version 3.0 */
-
-                                               packet = calloc(1, packet_len);
-
-                                               memcpy(packet + 4, &pv, 4);
-
-                                               /* copy "user" */
-                                               memcpy(packet + 8, "user", 4);
-
-                                               /* copy username */
-                                               memcpy(packet + 13, args[cur_arg+1], strlen(args[cur_arg+1]));
-
-                                               free(curproxy->check_req);
-                                               curproxy->check_req = packet;
-                                               curproxy->check_len = packet_len;
-
-                                               packet_len = htonl(packet_len);
-                                               memcpy(packet, &packet_len, 4);
-                                               cur_arg += 2;
-                                       } else {
-                                               /* unknown suboption - catchall */
-                                               ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
-                                                        file, linenum, args[0], args[1]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                               } /* end while loop */
-                       }
-                       if (alertif_too_many_args_idx(2, 1, file, linenum, args, &err_code))
+                       err_code |= proxy_parse_pgsql_check_opt(args, 0, curproxy, &defproxy, file, linenum);
+                       if (err_code & ERR_FATAL)
                                goto out;
                }
                else if (!strcmp(args[1], "redis-check")) {
index b0e770480373ddd63260728c16edad3fb3c8b038..d0452a9f8ac1a9caa3583a07e2655417c294c56f 100644 (file)
@@ -1156,27 +1156,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
                break;
        }
 
-       case PR_O2_PGSQL_CHK:
-               if (!done && b_data(&check->bi) < 9)
-                       goto wait_more_data;
-
-               /* do not reset when closing, servers don't like this */
-               if (conn_ctrl_ready(cs->conn))
-                       fdtab[cs->conn->handle.fd].linger_risk = 0;
-
-               if (b_head(&check->bi)[0] == 'R') {
-                       set_server_check_status(check, HCHK_STATUS_L7OKD, "PostgreSQL server is ok");
-               }
-               else {
-                       if ((b_head(&check->bi)[0] == 'E') && (b_head(&check->bi)[5]!=0) && (b_head(&check->bi)[6]!=0))
-                               desc = &b_head(&check->bi)[6];
-                       else
-                               desc = "PostgreSQL unknown error";
-
-                       set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-               }
-               break;
-
        case PR_O2_MYSQL_CHK:
                if (!done && b_data(&check->bi) < 5)
                        goto wait_more_data;
@@ -3512,7 +3491,7 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
 }
 
 
-static __maybe_unused struct tcpcheck_var *tcpcheck_var_create(const char *name)
+static struct tcpcheck_var *tcpcheck_var_create(const char *name)
 {
        struct tcpcheck_var *var = NULL;
 
@@ -5400,6 +5379,162 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
        goto out;
 }
 
+/* Parses the "option pgsql-check" proxy keyword */
+int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                               const char *file, int line)
+{
+       static char pgsql_req[] = {
+               "%[var(check.plen),htonl,hex]" /* The packet length*/
+               "00030000"                     /* the version 3.0 */
+               "7573657200"                   /* "user" key */
+               "%[var(check.username),hex]00" /* the username */
+               "00"
+       };
+
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk;
+       struct tcpcheck_var *var = NULL;
+       char *user = NULL, *errmsg = NULL;
+       size_t packetlen = 0;
+       int err_code = 0;
+
+       if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
+               err_code |= ERR_WARN;
+
+       if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
+               goto out;
+
+       if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
+               ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
+                        file, line);
+               err_code |= ERR_ALERT | ERR_FATAL;
+               goto out;
+       }
+
+
+       curpx->options2 &= ~PR_O2_CHK_ANY;
+       curpx->options2 |= PR_O2_TCPCHK_CHK;
+
+       free_tcpcheck_vars(&rules->preset_vars);
+       rules->list  = NULL;
+       rules->flags = 0;
+
+       cur_arg += 2;
+       if (!*args[cur_arg] || !*args[cur_arg+1]) {
+               ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
+                        file, line, args[0], args[1]);
+               goto error;
+       }
+       if (strcmp(args[cur_arg], "user") == 0) {
+               packetlen = 15 + strlen(args[cur_arg+1]);
+               user = strdup(args[cur_arg+1]);
+
+               var = tcpcheck_var_create("check.username");
+               if (user == NULL || var == NULL) {
+                       ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+                       goto error;
+               }
+               var->data.type = SMP_T_STR;
+               var->data.u.str.area = user;
+               var->data.u.str.data = strlen(user);
+               LIST_INIT(&var->list);
+               LIST_ADDQ(&rules->preset_vars, &var->list);
+               user = NULL;
+               var = NULL;
+
+               var = tcpcheck_var_create("check.plen");
+               if (var == NULL) {
+                       ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+                       goto error;
+               }
+               var->data.type = SMP_T_SINT;
+               var->data.u.sint = packetlen;
+               LIST_INIT(&var->list);
+               LIST_ADDQ(&rules->preset_vars, &var->list);
+               var = NULL;
+       }
+       else {
+               ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
+                        file, line, args[0], args[1]);
+               goto error;
+       }
+
+       rs = tcpcheck_ruleset_lookup("*pgsql-check");
+       if (rs)
+               goto ruleset_found;
+
+       rs = tcpcheck_ruleset_create("*pgsql-check");
+       if (rs == NULL) {
+               ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+               goto error;
+       }
+
+       chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
+                                    1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 0;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
+                                 1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 1;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
+                                              "min-recv", "5",
+                                              "error-status", "L7RSP",
+                                              "on-error", "%[check.payload(6,0)]",
+                                              ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 2;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
+                                              "min-recv", "9",
+                                              "error-status", "L7STS",
+                                              "on-success", "PostgreSQL server is ok",
+                                              "on-error",   "PostgreSQL unknown error",
+                                              ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 3;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       LIST_ADDQ(&tcpchecks_list, &rs->list);
+
+  ruleset_found:
+       rules->list = &rs->rules;
+       rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK);
+
+  out:
+       free(errmsg);
+       return err_code;
+
+  error:
+       free(user);
+       free(var);
+       free_tcpcheck_vars(&rules->preset_vars);
+       tcpcheck_ruleset_release(rs);
+       err_code |= ERR_ALERT | ERR_FATAL;
+       goto out;
+}
+
+
 static struct cfg_kw_list cfg_kws = {ILH, {
         { CFG_LISTEN, "tcp-check",  proxy_parse_tcpcheck },
         { 0, NULL, NULL },