Pool Member Status Page on a Virtual Server v9¶
Contributed by: Bhattman, based off Samples: pool_status_page¶
Description¶
This iRule will create a dynamic updated HTML page and/or xml rss page
of all the pools and their members address and ports and indicate
UP/DOWN or Disabled Status. It uses an external datagroup file, + cron
to make the list dynamic.
You apply the iRule to a virtual server with the HTTP profile. This
will allow clients or other IT personal see pools and their member
status w/o logging into the LTM. The page is useful if you have
separate monitors that can look at the content of the page.
Note: This was only tested on Version 9.3.x branch. v9.4.6 and .7 are
listed below
Here are the instructions:
1. Create a file in the following path on the LTM
/etc/cron.daily/pool_member_status_list.cron
which contains:
#!/bin/bash
# The command below is included because it was required when patched to 9.3.1 HF6
b db bigpipe.displayservicenames false
b pool all member all | grep pool_ | awk '{print "\""$3"\""}'> | sort >/var/class/pool_member_status_list.class
### Only performs a b load when it's on the active unit.###
if [ "`/bin/ps1`" == "Active" ]; then
b load
fi
exit 0
2. Change the permissions by entering:
chmod +755 /etc/cron.daily/pool_member_status_list.cron
3. Create a external datagroup
Main >> Local Traffic >> iRules
Select Data Group List
Click create
enter name: pool_member_status_list
enter Type: External
Path / Filename: /var/class/pool_member_status_list.class
File Content: String
iRule Source¶
when HTTP_REQUEST {
if { [HTTP::uri] eq "/status" } {
set response "<html><head><title>BIGIP Pool Member Status - \
[clock format [clock seconds]]</title><meta http-equiv='refresh' content='300;\
url=http://[HTTP::host]/status'></head><h1>BIGIP Pool Member Status - [clock format [clock seconds]]</h1>\
<table border='1'><tr><th>Status</th><th>Pool Name</th><th>Member</th><th>Port</th></tr>"
foreach { selectedpool } $::pool_member_status_list {
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
append response "<tr><td><font style=\"color:green\"><b>UP</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
"down" {
append response "<tr><td><font style=\"color:red\"><b>DOWN</b></font></td>\
<td>[string tolower $poolname]</<d><td>$addr</td><td>$port</td><tr>"
}
"session_enabled" {
append response "<tr><td><font style=\"color:blue\"><b>ENABLED</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
"session_disabled" {
append response "<tr><td><font style=\"color:black\"><b>DISABLED</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
Default {
append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
}
#SWITCH END
} errmsg] } {
append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
}
append response "</table></body></html>"
HTTP::respond 200 content $response "Content-Type" "text/html" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
if { [HTTP::uri] eq "/rss" } {
set response "<?xml version=\"1.0\" encoding=\"utf-8\"?><rss version=\"2.0\"><channel> \
<title>BigIP Server Pool Status</title><description>Server Pool Status</description> \
<language>en</language><pubDate>[clock format [clock seconds]]</pubDate>\<ttl>60</ttl>"
foreach { selectedpool } $::pool_member_status_list {
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:green\">UP</font></b></description></item>"
}
"down" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:red\">DOWN</font></b></description></item>"
}
"session_enabled" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:blue\">ENABLED</font></b></description></item>"
}
"session_disabled" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:black\">DISABLED</font></b></description></item>"
}
Default {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:orange\">INVALID</font></b></description></item>"
}
}
#SWITCH END
} errmsg] } {
append response "<item><title>[string tolower $poolname] Status</title><description>Member $addr:$port is <b>\
<font style=\"color:orange\">INVALID</font></b></description></item>"
}
}
append response "</channel></rss>"
HTTP::respond 200 content $response "Content-Type" "text/xml" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
}
Note: This was verified to work version 9.4.6 and .7 releases
This code was reworked by David Larsen¶
Here are the instructions:
1. Create a file in the following path on the LTM
/etc/cron.daily/pool_member_status_list.cron
which contains:
#!/bin/bash
b db bigpipe.displayservicenames false
b pool all |grep "POOL MEMBER" | awk '{sub(":any",":0",$4);print "\""$4"\","}' | sort >/var/class/pool_member_status_list.class
b load
exit 0
2. Change the permissions by entering:
chmod +755 /etc/cron.daily/pool_member_status_list.cron
3. Create a external datagroup
Main >> Local Traffic >> iRules
Select Data Group List
Click create
enter name: pool_member_status_list
enter Type: External
Path / Filename: /var/class/pool_member_status_list.class
File Content: String
4. change Bigpipe database “b db bigpipe.displayservicenames false” to
false so that POOL MEMBERS will LIST PORT rather then display the
service names such as HTTP, FTP, HTTPS.
iRule Source¶
when HTTP_REQUEST {
if { [HTTP::uri] eq "/status" } {
set response "<html><head><title>BIGIP Pool Member Status - \
[clock format [clock seconds]]</title><meta http-equiv='refresh' content='300;\
url=http://[HTTP::host]/status'></head><h1>BIGIP Pool Member Status - [clock format [clock seconds]]</h1>\
<table border='1'><tr><th>Status</th><th>Pool Name</th><th>Member</th><th>Port</th></tr>"
foreach { selectedpool } $::pool_member_status_list {
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
append response "<tr><td><font style=\"color:green\"><b>UP</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
"down" {
append response "<tr><td><font style=\"color:red\"><b>DOWN</b></font></td>\
<td>[string tolower $poolname]</<d><td>$addr</td><td>$port</td><tr>"
}
"session_enabled" {
append response "<tr><td><font style=\"color:blue\"><b>ENABLED</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
"session_disabled" {
append response "<tr><td><font style=\"color:black\"><b>DISABLED</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
Default {
append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
}
#SWITCH END
} errmsg] } {
append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td>[string tolower $poolname]</td><td>$addr</td><td>$port</td><tr>"
}
}
append response "</table></body></html>"
HTTP::respond 200 content $response "Content-Type" "text/html" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
if { [HTTP::uri] eq "/rss" } {
set response "<?xml version=\"1.0\" encoding=\"utf-8\"?><rss version=\"2.0\"><channel> \
<title>BigIP Server Pool Status</title><description>Server Pool Status</description> \
<language>en</language><pubDate>[clock format [clock seconds]]</pubDate>\<ttl>60</ttl>"
foreach { selectedpool } $::pool_member_status_list {
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:green\">UP</font></b></description></item>"
}
"down" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:red\">DOWN</font></b></description></item>"
}
"session_enabled" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:blue\">ENABLED</font></b></description></item>"
}
"session_disabled" {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:black\">DISABLED</font></b></description></item>"
}
Default {
append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:orange\">INVALID</font></b></description></item>"
}
}
#SWITCH END
} errmsg] } {
append response "<item><title>[string tolower $poolname] Status</title><description>Member $addr:$port is <b>\
<font style=\"color:orange\">INVALID</font></b></description></item>"
}
}
append response "</channel></rss>"
HTTP::respond 200 content $response "Content-Type" "text/xml" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
}
ALTERNATE VERSION¶
NOTE: Modified a version by (joelmoses) taken from
Pool__Member__Status__Page_on_a__Virtual__Server_v10
to work with the 9.x branch - Bhattman.
It adds parameter handling to allow a user to specify a filter for a
particular pool (one at a time) by calling it with the p= parameter.
It also has error handling for unknown calls and checking for
cross-site scripting attempts via the generated error pages.
To call a particular pool, use this format:
http://<status_vip>/status?p=poolname_here
or
http://<status_vip>/rss?p=poolname_here
when RULE_INIT {
######################################################
# BEGIN CONFIGURATION
# The name of your company.
set ::org_name "OrgName"
# Base login URL for your loadbalancer. This should be set to the floating IP in the case of HA BigIPs.
# It should be in URL form without trailing slash (e.g., "https://10.1.1.1").
set ::lb_address "https://10.10.10.10"
# The friendly name of this particular load balancer group.
set ::lb_name "Load Balancer Group One"
######################################################
# PAGE CONTENT BELOW THIS POINT
# Note: All delivered pages are set to not cache at the browser and immediately expire.
# HTML page displayed when an error condition must be handled.
set error_404 {
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>404 Not Found</title>
<style><!--
#body \{font-family: arial,sans-serif\}
#div.nav \{margin-top: 1ex\}
#div.nav A \{font-size: 10pt; font-family: arial,sans-serif\}
#span.nav \{font-size: 10pt; font-family: arial,sans-serif; font-weight: bold\}
#div.nav A,span.big \{font-size: 12pt; color: #0000cc\}
#div.nav A \{font-size: 10pt; color: black\}
#A.l:link \{color: #6f6f6f\}
#A.u:link \{color: green\}
//--></style>
</head>
<body text=#000000 bgcolor=#ffffff>
<table border=0 cellpadding=2 cellspacing=0 width="100%"><tr><td rowspan=3 width="1%" nowrap>
<b><font face=times color=#0039b6 size=10>$::org_name </font></b>
<td> </td></tr>
<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff> <b>404 (Page not found) Error</b></td></tr>
<tr><td> </td></tr></table>
<blockquote>
<H1>Not Found</H1>
The requested URL <code>$HTTP_URI</code> was not found on this server.
<p>
</blockquote>
<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc><img alt="" width=1 height=4></td></tr></table>
</body></html>
}
# HTML page displayed bookending the generated table of pool members.
set page_200 {
<html><head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>Pool Member Status - $::lb_name</title>
<style><!--
#body \{font-family: arial,sans-serif\}
#div.nav \{margin-top: 1ex\}
#div.nav A \{font-size: 10pt; font-family: arial,sans-serif\}
#span.nav \{font-size: 10pt; font-family: arial,sans-serif; font-weight: bold\}
#div.nav A,span.big \{font-size: 12pt; color: #0000cc\}
#div.nav A \{font-size: 10pt; color: black\}
#A.l:link \{color: #6f6f6f\}
#A.u:link \{color: green\}
//--></style>
</head>
<body text=#000000 bgcolor=#ffffff>
<table border=0 cellpadding=2 cellspacing=0 width="100%"><tr><td rowspan=3 width="1%" nowrap>
<b><font face=times color=#0039b6 size=10>$::org_name </font></b>
<td> </td></tr>
<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff> <b>Pool Node Status - ($::lb_name)</b></td></tr>
<tr><td> </td></tr></table>
<blockquote>
<h2>$timestamp</h2>
<p><b>Note:</b> <i>Access to the pool links is restricted to load balancer administrators. Click <a href=\"$::lb_address\">here</a> to log in first.</i></p>
$response
<p>
</blockquote>
<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor=#3366cc><img alt="" width=1 height=4></td></tr></table>
</body></html>
}
# END CONFIGURATION
######################################################
}
when HTTP_REQUEST {
######################################################
# SETUP AND DATA COLLECTION
# Catch attempts to do cross-site scripting to our generated HTML. Hopefully
# you're not generating the status page in your trusted sites zone in IE... but
# if you are...
if { [HTTP::uri] matches_regex {((\%3C)|<)[^\n]+((\%3E)|>)} } {
set HTTP_URI " !! *withheld* - CSS attempt !! "
} else {
set HTTP_URI [HTTP::uri]
}
# Process the querystring from the HTML request if there is one. Ignore all but p=.
if { [HTTP::query] ne "" } {
set query 1
set namevals [split [HTTP::query] "&"]
for {set i 0} {$i < [llength $namevals]} {incr i} {
set params [split [lindex $namevals $i] "="]
set pnum [expr $i+1]
if { ([lindex $params 0] eq "p") } {
set manualpool "[URI::query [HTTP::uri] [lindex $params 0]]"
}
}
} else {
set query 0
}
######################################################
# PAGE GENERATION - HUMAN READABLE STATUS
# Generate the status page with either all content or content from a single pool.
if { ([HTTP::uri] eq "/status") || (([HTTP::uri] starts_with "/status?") && ($query) && [info exists manualpool]) } {
set timestamp "[clock format [clock seconds]]"
set response "<table border='1'><tr><th>Status</th><th>Pool Name</th><th>Member</th><th>Port</th></tr>"
set count 0
foreach { selectedpool } $::pool_member_status_list {
set display 1
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:green\"><b>UP</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
"down" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:red\"><b>DOWN</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
"session_enabled" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:blue\"><b>ENABLED</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
"session_disabled" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:black\"><b>DISABLED</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
Default {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
}
#SWITCH END
} errmsg] } {
# All else has failed. Punt.
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<tr><td><font style=\"color:orange\"><b>INVALID</b></font></td>\
<td><a href=\"$::lb_address/tmui/Control/jspmap/tmui/locallb/pool/stats.jsp?name=[string tolower $poolname]\">[string tolower $poolname]</a></td><td>$addr</td><td>$port</td><tr>"
incr count }
}
}
# If we made it all the way through the generation of the table and didn't display anything, there must
# have been no matching pool.
if { $count == 0 } {
append response "<tr><td colspan=4><center>No pool members selected.</center></td></tr></table>"
} else {
append response "</table>"
}
HTTP::respond 200 content [subst $::page_200] "Content-Type" "text/html" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
######################################################
# PAGE GENERATION - RSS FEED
# Generate an RSS feed element with either all content or content from a single pool.
if { ([HTTP::uri] eq "/rss") || (([HTTP::uri] starts_with "/rss?") && ($query) && [info exists manualpool]) } {
set response "<?xml version=\"1.0\" encoding=\"utf-8\"?><rss version=\"2.0\"><channel> \
<title>Server Pool Status ($::lb_name)</title><description>Server Pool Status ($::lb_name)</description> \
<language>en</language><pubDate>[clock format [clock seconds]]</pubDate>\<ttl>60</ttl>"
set count 0
foreach { selectedpool } $::pool_member_status_list {
set display 1
if { [catch {
scan $selectedpool {%[^/]/%[^:]:%s} poolname addr port
switch -glob [LB::status pool $poolname member $addr $port] {
"up" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:green\">UP</font></b></description></item>"
incr count }
}
"down" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:red\">DOWN</font></b></description></item>"
incr count }
}
"session_enabled" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:blue\">ENABLED</font></b></description></item>"
incr count }
}
"session_disabled" {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:black\">DISABLED</font></b></description></item>"
incr count }
}
Default {
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description> \
Member $addr:$port is <b><font style=\"color:orange\">INVALID</font></b></description></item>"
incr count }
}
}
#SWITCH END
} errmsg] } {
# All else has failed. Punt.
if { ($query) && ($manualpool ne "") && ($poolname ne $manualpool) } { set display 0 } else { set display 1 }
if {$display} { append response "<item><title>[string tolower $poolname] Status</title><description>Member $addr:$port is <b>\
<font style=\"color:orange\">INVALID</font></b></description></item>"
incr count }
}
}
if { $count == 0 } {
append response "<item><title>No such pool</title><description> \
<b>No such pool</b></description></item></channel></rss>"
} else {
append response "</channel></rss>"
}
HTTP::respond 200 content $response "Content-Type" "text/xml" "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
######################################################
# PAGE GENERATION - ERROR HANDLING AND CLEANUP
# Handle any errors (unknown page requests, bad query strings) and unset my status.
if { (([HTTP::uri] eq "/status") || (([HTTP::uri] starts_with "/status?") && ($query) && [info exists manualpool])) || (([HTTP::uri] eq "/rss") || (([HTTP::uri] starts_with "/rss?") && ($query) && [info exists manualpool])) } {
unset display
unset query
if { [info exists manualpool] } { unset manualpool }
} else {
unset query
HTTP::respond 404 content [subst $::error_404] "Cache-Control" "no-cache, must-revalidate" "Expires" "Mon, 26 Jul 1997 05<!--:00:00 GMT"-->
}
}
The BIG-IP API Reference documentation contains community-contributed content. F5 does not monitor or control community code contributions. We make no guarantees or warranties regarding the available code, and it may contain errors, defects, bugs, inaccuracies, or security vulnerabilities. Your access to and use of any code available in the BIG-IP API reference guides is solely at your own risk.