Hello, I have multiple HAProxy servers that we load balance with IPVS, and I wanted the ability to natively health check them using the CSV stats page, rather than just using the http service check, as this would require a separate health check configuration and port for each server/service that we proxy. This configuration quickly gets unwieldy. I figured that I could use the one single location CSV stats page to service check all the servers we proxy. Once this patch is applied, you will need the Text:CSV perl module for the service check to function. http://search.cpan.org/~makamaka/Text-CSV-1.21/ To configure ldirectord to service check HAProxy, set the service line to haproxy, the checkport to the port that the HAProxy stats page is listening on, the request line to the relative URI for the HAProxy CSV stats page and finally set the receive line to the proxy, service and status to check in the CSV output. These three entries correspond to the "pxname", "svname" and "status" fields respectively (see documentation link below). Anything that appears in the HAProxy CSV stats page in those fields can be entered in the receive line. Here is an example of what the HAProxy CSV stats page looks like for reference: http://demo.1wt.eu/;csv And, some documentation on the HAProxy stats page: http://cbonte.github.com/haproxy-dconv/configuration-1.4.html#9 So a configuration would look as follows: service = haproxy checkport = 80 request = "/;csv" receive = "www,bck,UP" This would check the stats of the "www" proxy, the "bck" service and if the status is "UP". I'm open to any comments, or suggestions. Also, if you feel that this feature is beneficial I would appreciate if it was merged into the ldirectord mainline. The CSV stats are very extensive, so this patch could be expanded in numerous ways if anyone is inclined. I've also CC'd the HAProxy mailing list in case anyone on that list uses ldirectord and would find this patch useful. Thanks. --- /usr/sbin/ldirectord 2012-04-27 05:40:43.000000000 -0600 +++ /usr/sbin/ldirectord_haproxy 2012-12-27 11:16:44.570384155 -0700 @@ -448,7 +448,7 @@ On means no checking will take place and real servers will always be activated. Default is I<negotiate>. -B<service = >B<dns> | B<ftp> | B<http> | B<https> | B<http_proxy> | B<imap> | B<imaps> | B<ldap> | B<mysql> | B<nntp> | B<none> | B<oracle> | B<pgsql> | B<pop> | B<pops> | B<radius> | B<simpletcp> | B<sip> | B<smtp> | B<submission> +B<service = >B<dns> | B<ftp> | B<haproxy> | B<http> | B<https> | B<http_proxy> | B<imap> | B<imaps> | B<ldap> | B<mysql> | B<nntp> | B<none> | B<oracle> | B<pgsql> | B<pop> | B<pops> | B<radius> | B<simpletcp> | B<sip> | B<smtp> | B<submission> The type of service to monitor when using checktype=negotiate. None denotes a service that will not be monitored. @@ -535,6 +535,9 @@ Number of port to monitor. Sometimes check port differs from service port. +If the service is HAProxy, the checkport must be set to the port that +HAProxy stats page is listening on. + Default: port specified for each real server B<request = ">I<uri to requested object>B<"> @@ -557,6 +560,9 @@ For a simpletcp check, this string is sent verbatim except any occurrences of \n are replaced with a new line character. +For a HAProxy check, this string should be the relative URI for the HAProxy +CSV stats page. + B<receive = ">I<regexp to compare>B<"> If the requested result contains this I<regexp to compare>, the real server @@ -570,6 +576,11 @@ For a MySQL check, the receive setting is not used. +For a HAProxy check, this should be the proxy, service and status to check. +It should be in the format "proxy,service,status". These correspond to +the "pxname", "svname" and "status" CSV fields respectively. Anything that +appears in the HAProxy CSV stats page in those fields can be entered here. + B<httpmethod = GET> | B<HEAD> Sets the HTTP method which should be used to fetch the URI specified in @@ -1490,6 +1501,7 @@ } elsif ($rcmd =~ /^service\s*=\s*(.*)/) { $1 =~ /(\w+)/ && ($1 eq "dns" || $1 eq "ftp" || + $1 eq "haproxy" || $1 eq "http" || $1 eq "https" || $1 eq "http_proxy" || @@ -1511,6 +1523,7 @@ or &config_error($line, "service must " . "be dns, ftp, " . + "haproxy, " . "http, https, " . "http_proxy, " . "imap, imaps, " . @@ -2729,6 +2742,8 @@ $$r{num_connects} = 0 if (check_oracle($v, $r) == $SERVICE_UP); } elsif ($$v{service} eq "simpletcp") { $$r{num_connects} = 0 if (check_simpletcp($v, $r) == $SERVICE_UP); + } elsif ($$v{service} eq "haproxy") { + $$r{num_connects} = 0 if (check_haproxy($v, $r) == $SERVICE_UP); } else { $$r{num_connects} = 0 if (check_none($v, $r) == $SERVICE_UP); } @@ -3640,6 +3655,83 @@ return $SERVICE_DOWN; } +sub check_haproxy +{ + use LWP::Simple; + use Text::CSV; + + my ($v, $r) = @_; + my $port = $$v{checkport}; + my @haproxy_fields = split(",", lc($$r{receive})); + my $not_found = 0; + + if (!defined($port)) { + &ld_debug(2, "The checkport line is not configured and must be set when using haproxy as the service."); + &ld_debug(2, "Configure the port that the haproxy stats page is listening on to service check proxied servers."); + + service_set($v, $r, "down", {do_log => 1}); + return $SERVICE_DOWN; + } + + if (!defined($haproxy_fields[0]) or !defined($haproxy_fields[1]) or !defined($haproxy_fields[2])) { + &ld_debug(2, "The receive line is not configured correctly, or at all."); + &ld_debug(2, "Use the format receive = \"proxy,service,status\" when using haproxy as the service."); + + service_set($v, $r, "down", {do_log => 1}); + return $SERVICE_DOWN; + } + + &ld_debug(2, "Checking $$v{service} stats at: http://$$r{server}:$port$$r{request}"); + + &ld_debug(2, "Checking proxy: \"$haproxy_fields[0]\" service: \"$haproxy_fields[1]\" for status: \"$haproxy_fields[2]\""); + + my $csv = Text::CSV->new({ + binary => 1 + }); + + my $csv_file = get('http://' . $$r{server} . ':' . $port . $$r{request}); + + open (CSV, "<", \$csv_file); + + while (<CSV>) { + next if ($. == 1); + + if ($csv->parse($_)) { + my @columns = $csv->fields(); + + @columns = map {lc} @columns; + + if ($columns[0] eq $haproxy_fields[0]) { + if ($columns[1] eq $haproxy_fields[1]) { + if ($columns[17] eq $haproxy_fields[2]) { + &ld_debug(2, "Status of \"$haproxy_fields[2]\" found for proxy: \"$columns[0]\" and service: \"$columns[1]\""); + + close CSV; + service_set($v, $r, "up", {do_log => 1}); + return $SERVICE_UP; + } + } + } + } + + $not_found = 1; + } + + close CSV; + + if ($not_found eq 1) { + &ld_debug(2, "Proxy: \"$haproxy_fields[0]\" service: \"$haproxy_fields[1]\" or status: \"$haproxy_fields[2]\" not found."); + + } else { + &ld_debug(2, "Unable to retrieve $$v{service} stats at: http://$$r{server}:$port$$r{request}"); + } + + &ld_debug(2, "Verify your checkport, request and receive configuration settings."); + + service_set($v, $r, "down", {do_log => 1}); + return $SERVICE_DOWN; +} + # check_none # Dummy function to check service if service type is none. # Just activates the real server -- To unsubscribe from this list: send the line "unsubscribe lvs-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html