LTM Maintenance Page

Contributed by: spark, based off several other contributors including citizen_elah

UPDATE: Should now work for v10.x

Description

How to use the LTM as a webserver for a particular directory. In this case, we use it to return a maintenance page with text and images to return when no pool members are available. The maintenance pages are always served up, even if the pool is active. This is partly to handle the case where the pool comes back up while the client is loading all the parts of the maintenance page, and partly to show how to use the LTM as a rudimentary web server.
1. Create the HTML for the maintenance page. It must be on one line, and all double-quotes must be escaped with backslashes., the code must start with ” and end with ” .
2. Save it to a file /var/class/maint.index.html.class on the LTM.
3. Upload all desired images to /var/images on the BigIP (create the directory as needed).
4. Convert images into base64 format string:
#!/bin/bash

## clear images class
echo -n "" > /var/class/images.class

## loop through real images and create base64 data for images class
for i in $(ls /var/images); do
        echo "\"`echo \"$i\"|tr '[:upper:]' '[:lower:]'`\" := \"`base64 /var/images/$i|tr -d '\n'`\"," >> /var/class/images.class
done
5. Note that this creates a file called /var/class/images.class – always run “b load” after creating or updating an external class file!
6. Use the configuration below, changing the error condition and maintenance prefix if needed.
You can easily extend this example to serve up multiple pages, both text and not. You could also just use variables for these files instead of classes, but it seems much more risky to me to edit the actual bigip.conf file or iRule than it does to just edit one file under /var/class/ and do a b load.
Note: The file content is stored in LTM memory upon configuration load, so you should be cautious about serving large amounts of data from the classes.

Configuration, including iRule

The class containing the HTML page will be defined as a Data Group List in the GUI: Local Traffic -> iRules -> Data Group List -> Create…

  • Name: maint_index_html_class
  • Type: (External File)
  • Path/Filename: /var/class/maint.index.html.class
  • File Contents: String
  • Key/Value Pair Separator: :=
  • Access Mode: Read Only

The class containing the base64 encoded image will be defined as a Data Group List in the GUI: Local Traffic -> iRules -> Data Group List -> Create…
  • Name: images_class
  • Type: (External File)
  • Path/Filename: /var/class/images.class
  • File Contents: String
  • Key/Value Pair Separator: :=
  • Access Mode: Read Only

Note: in case of error reduce the size of the image files
iRule which references the HTML and logo class:
when RULE_INIT {
   # set DEBUG to 1 for debug log output
   set static::DEBUG 0
}

when HTTP_REQUEST {

   # Service requests for files (URIs) from the maintenance page.
   # Note that we always service these pages, even if the http_pool is up.
   # The maintenance prefix should be unique in the application and
   #   not conflict with an existing directory on the web servers.
   set maint_prefix "/maintenancepage"

   if { [HTTP::uri] starts_with "$maint_prefix" } {

      # Strip off the $maint_prefix from the URI as you can't easily do variable expansion
      # within a switch statement.
      # Note that requests for the exact maintenance prefix URI will be set to a null string,
      # so handle null in the first switch case.
      set uri [string map [list $maint_prefix ""] [HTTP::uri]]

      # Return the requested page based on the requested URI
      switch -glob -- $uri {
         "" {
            if { $static::DEBUG } { log local0. "Request for $maint_prefix. Redirecting to $maint_prefix/" }
            HTTP::redirect "$maint_prefix/index.html"
         }
         "/" -
         "/index.html" {
            if { $static::DEBUG } { log local0. "Request for index. Responding with content: [class element -value 0 maint_index_html_class]" }
            HTTP::respond 200 content [class element -value 0 maint_index_html_class] "Content-Type" "text/html"
         }
         "/*.png" {
            # NOTE: '17' is derived by the length of /maintenancepage/
            if { [class match [findstr [string tolower [HTTP::path]] "/" 17] equals images_class] } {
               if { $static::DEBUG } { log local0. "Request for [findstr [HTTP::path] "/" 17] found. Responding with binary content" }
               HTTP::respond 200 content [b64decode [class lookup [findstr [HTTP::path] "/" 17] images_class]] "Content-Type" "image/png"
            } else {
               if { $static::DEBUG } { log local0. "Request for [findstr [HTTP::path] "/" 1] NOT found.  Ignoring..." }
            }
         }
         default {
            if { $static::DEBUG } { log local0. "Unrecognized request to URI: [HTTP::uri]" }
            HTTP::respond 404 content "Unrecognized request to [HTTP::uri]" "Content-Type" "text/html"
         }
      }
      return
   }
   #
   # If the all members in the default pool are down, redirect to the maintenance page
   #
   if { [active_members [LB::server pool]] < 1 } {
      if { $static::DEBUG } { log local0. "All Pool Members down: Redirecting to $maint_prefix/index.html" }
      HTTP::redirect "$maint_prefix/index.html"
   }
}
virtual servertest {
   pool http_pool
   destination 10.1.2.3:http
   ip protocol tcp
   rule webserver
   profiles
      http
      tcp
}

Contents of the class files

/var/class/maint.index.html.class

"key" := "<html><head><title>Maintenance page</title></head><body><img src=\"logo.png\"><p><h2>Sorry! This site is down for maintenance.</h2></body></html>",

/var/class/images.class (all one line in reality, split here for the sake of readability. Note the quotes & trailing comma are REQUIRED)
"logo.png" := "iVBORw0KGgoAAAANSUhEUgAAAL4AAAC3AgMAAADRFQzGAAAACVBMVEUAAADDAAD///8UoaPIAAAD
hElEQVRo3u3ZOW7dMBAAUB8lSBXkPmTB3g1PEqSf3jAwgYenTP4icVYuXpDmq/Dyv564SSNy+NT2
jpenB3iALYAp7wBI12MZpOOoayClgXAAMJAWAPLzbdMtSPIoM4AKpBnQ5+siNDAF6CI0sOerIhRw
ClAdpUDyjhoDckGOAbggxcA/XzRbAApAjkBQI1EnAaLzeT9xQCHIPkB2QYrqxAHwy0FQJw5E8Rh0
LAMkSw8awQDKa4HfCAZAXgpvVdeNYED3+HFdFAV3QPomgLMiyL7oAPWtj6zm/c8O4Ggb3S9HvDyw
4Kwp3H/7AfwEdNboKGkFiMaDiXoC4FEjOro3jQEcNcL7vUALgN91FVV8qQqco3YHGRQoEtA5/BhE
MAdUDUTlswS4C842R4CSAebZzBJUAdI7QJmAwgF54N8HaQSqB1K/YzIHOALX72IAbKBZwHy+9/Id
9GGIQPoKoANsZSB9CSgdkAeyDsgfBXkKcgfogaJfqwtANjsAZxQp5k00AdXp5wOAnaCyIAMeaAPQ
FoGdU9RVcFSqgzQBNAcgew1mgEyQmQDQ43KOvQPQmxCjBX/UMJVm6sSBvfmKnZSfgDxQ7UJnC+Au
oClw1kYSlAmAXYATkB2QtwDtltAmoHwcwCeD6qz6/zOwsz3avDWmwLnB5SNqgRptmjzTwQRoDKRY
AUIsAX6LiFAZAv5eWQPZAzAAvYhVkHdBX2y2RVAswCHIuyBtg1s/UV4GxQAag/xOUJZBMqB9BnjW
3YRVTqUVyLgJCs1A0pFPDwToBYeOfHtATotLXyqyRZMARcxxF0A1gIpe+Akg81NzkGX+p5y9yhev
A1BHAE8gAz+YBbiY8smlQG3nsnQNOCt2UmvRKrqpdxJPUxjQ+BOHE1A4uPwD1zj5k2dObG6Nz0iv
X/9+5ckcC5BnFi8ffPsRZtYzX2hcCrt88Pb6ywHEIgur0eXH24tXQmOBAnuNalgCG7n+LoNbH353
AU/gHU/i/QK8l3Q7qrejwseh0QC4qcfG3uEqP4Z+chP6WZT0ui/Otza9gheB18voXs4k/kYPU8DN
fVfRIMkcJtajNPZ2ojxOxZfdZH+Qu3d3UIbbCdsbFlGdaghwd9OlTWu0snGURhtHtLs1NZheBYAm
W1n2mYbdDTza3SLUHbWwzRlvrS1spJalnVcanB9Evvj8aLuZ3BX1Y1P+AUbgLzbWCp20NpfmAAAA
AElFTkSuQmCC",

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.