Forum Discussion

GavinW_29074's avatar
GavinW_29074
Icon for Nimbostratus rankNimbostratus
Nov 04, 2011

Stats Profile funnies...

Hi there,

 

 

I'm trying to implement an iRule for a dyncamic maintenance page using the following iRule: http://devcentral.f5.com/wiki/iRules.LTMMaintenanceWindow.ashx

 

 

However I'm having some weird issues with the stats profile...

 

 

It's almost like the stats profile is being stored differently for different sessions?!

 

Is this possible???

 

 

E.g. the following is a log extract from the above iRule... I've put in some additional logging to output the current values stored for the start and end times...

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $day: 5, $start_time: 940, $end_time: 950

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time less than 4 characters

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time now 0940

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $cur_day: 5, $cur_time: 1224

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

Nov 4 12:24:15 tmm info tmm[6377]: Rule /Common/MaintenancePage : $day: 5, $start_time: 1220, $end_time: 1300

 

Nov 4 12:24:15 tmm info tmm[6377]: Rule /Common/MaintenancePage : $cur_day: 5, $cur_time: 1224

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $day: 5, $start_time: 940, $end_time: 950

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time less than 4 characters

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time now 0940

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $cur_day: 5, $cur_time: 1224

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $day: 5, $start_time: 940, $end_time: 950

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time less than 4 characters

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time now 0940

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $cur_day: 5, $cur_time: 1224

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $day: 5, $start_time: 940, $end_time: 950

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time less than 4 characters

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $start_time now 0940

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/MaintenancePage : $cur_day: 5, $cur_time: 1224

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

Nov 4 12:24:15 tmm2 info tmm2[6379]: Rule /Common/splunk_http : Sending log to Splunk...

 

 

 

As you can see from the above, some lines have a start_time and end_time of 940/950, and some have 1220/1300... 1220/1300 is the last value i've set...

 

 

Any ideas???

 

 

Cheers

 

Gavin

 

10 Replies

  • Hmm, might be something or noting... But, i've had a closer look at the logs, and I can see that the different times being returned and being returned by different 'tmm' process'...

     

     

    tmm in the above log has the correct value, but tmm2 has the incorrect value...

     

     

    Is this a process sync issue?
  • spark_86682's avatar
    spark_86682
    Historic F5 Account
    Each TMM only has read access to its own values for STATS profiles. Only processes outside of tmm (like tmsh, for instance) have access to combined stats across all TMMs. It is possible that that iRule was written when all or most systems only had one TMM. My suggestion is to use the session table (by using the table iRule command) in place of the STATS:: commands.
  • Ok, looks like I'm rewriting the rule to use the session table then...

     

     

    I'm thinking of re-working it slightly, so that instead of a day, start time and end time being passed in, only a start date-time and duration is required...

     

     

    However I'm having some issues getting a string converted into a valid DT object...

     

     

    "Clock scan" looks like the most appropriate, but it doesn't appear possible to specify a format option...

     

     

    Any pointers?

     

     

    Cheers

     

    Gavin

     

  • OK, looks like I've managed to get a workable solution in place...

     

     

    Below is the code that I've created. Thought I'd post it here for comment before submitting to Code Share.

     

     

    Cheers

     

    Gavin

     

     

    when RULE_INIT {
    
      set static::SUBTABLE "maintenance_window"
      set static::debug 1
    
       Global variable to use within other iRules to prevent multiple invocation errors. 
      set ::MAINTENANCE_MODE 0
      
    }
    
    
    when HTTP_REQUEST priority 10 {
      
      set VS_NAME [virtual name]
      set TITLE "[substr $VS_NAME 8] iRule Maintenance Window Control"
      set PROFILE [substr $VS_NAME 8]
      set PROFILE_NAME [concat [string map {. _} $PROFILE]_maintenance_window]
      
      if {$static::debug == 1} { log local0.info "VS Name: $VS_NAME Title: $TITLE Profile: $PROFILE Profile Name: $PROFILE_NAME" }
    
       Look for embedded commands to control the maintenance window.
    
      switch -glob [string tolower [HTTP::uri]] {
        "/enable*" {
    
           Extract the maintenance window day, start date time, and duration from the URI. 
           If not there, return an error message.
    
          set params [split [HTTP::uri] "/"]
          if { [llength $params] < 4 } {
            HTTP::respond 200 content "
              $TITLE
              Usage: http://[HTTP::host]/enable/start_date_time/duration
      Start Date Time must be of the format yyyy-mm-dd hh24:mi:ss
      Duration is in Hours
      
              "
          } else {
    
             Assign the values to variables. 
    
    set START_VALUE [string map {"%20" " "} [lindex $params 2] ]
    set DURATION_HOURS [lindex $params 3]
    if {$static::debug == 1} { log local0.info "Start Value = $START_VALUE, Duration = $DURATION_HOURS." }
    
     Process input
            set START_EPOCH [clock scan $START_VALUE ]
    if {$static::debug == 1} { log local0.info "Start EPOCH = $START_EPOCH" }
    
    set DURATION_SECS [expr {$DURATION_HOURS*60*60}]
    if {$static::debug == 1} { log local0.info "Duration = $DURATION_SECS" }
    
     Add value to subtables
    table set -subtable $static::SUBTABLE $PROFILE_NAME $START_EPOCH 0 $DURATION_SECS
    
            HTTP::respond 200 content "
              $TITLE
              Maintenance Window enabled
              "
          }
        }
        "/disable" {
    
           By removing the session table entries, the maintenance is removed.
      if {$static::debug == 1} { log local0.info "Removing set Maintenance window..." }
          table delete -subtable $static::SUBTABLE $PROFILE_NAME
      
          HTTP::respond 200 content "
            $TITLE
            Maintenance Window disabled
            "
        }
        "/check" {
    
         Return the current time and the current settings for the maintenance window
    set START_EPOCH [table lookup -subtable $static::SUBTABLE $PROFILE_NAME]
     Set Common variables. 
    set NOW_EPOCH [clock seconds]
    set NOW_DTS [clock format $NOW_EPOCH ]
    
    if { $START_EPOCH != "" } {
    set START_DTS [clock format $START_EPOCH]
    set DURATION [table lifetime -subtable $static::SUBTABLE $PROFILE_NAME]
    set DIFFERENCE [expr { $NOW_EPOCH - $START_EPOCH } ]
    set RESPONSE "$START_DTS for [expr {$DURATION/60/60}] hours"
    
     Debug Logging
    if {$static::debug == 1} { 
    log local0.info "Maintenance window set..."
    log local0.info "Table values: Start EPOCH - $START_EPOCH Duration - $DURATION" 
    log local0.info "Start DTS = $START_DTS Date Time now = $NOW_EPOCH" 
    log local0.info "Difference = $DIFFERENCE"
    }
    } else {
    set START_DTS "No Maintenance Window set"
    set RESPONSE $START_DTS
    
     Debug Logging
    if {$static::debug == 1} { log local0.info "No Maintenance Window set..." }
    }
    
    HTTP::respond 200 content "
      $TITLE
      Current Date and time: $NOW_DTS
    Maintenance Window: $RESPONSE
      "
        }
        "/usage" {
    
             Usage instructions for the commands
    
            HTTP::respond 200 content "
              $TITLE
              Usage: http://[HTTP::host]/\[command\]
              
                /enable/start_date_time(yyyy-mm-dd hh24:mm:ss)/duration(h) - enable maintenance window.
                /disable - disable maintenance window.
                /check - check if in maintenance window.
                /usage - display this usage message
              
              "
        }
        default {
    
     no secret command entered, so check the maintenance window
    
     Get the values from the statistics profile.
    set START_EPOCH [table lookup -subtable $static::SUBTABLE $PROFILE_NAME]
    set DURATION [table lifetime -subtable $static::SUBTABLE $PROFILE_NAME]
    
     Debug logging 
    if {$static::debug == 1} { log local0.info "Table values: Start EPOCH - $START_EPOCH Duration - $DURATION" }
    
     Check if start_epoch is non-null. 
    if { $START_EPOCH != "" } {
    
             Use the TCL "clock" command to get the current time as epoch.
            set NOW_EPOCH [clock seconds]
            if {$static::debug == 1} { log local0.info "Current EPOCH Time = $NOW_EPOCH" }
            
     Check NOW against Scheduled start
    if { $NOW_EPOCH < $START_EPOCH } {
     Not yet in maintenance, therefore return for other processing
    return
    
    } else {
     Now must be greater than Start Epoch value, therefore in maintenance or after maintenance
    set DIFFERENCE [expr { $NOW_EPOCH - $START_EPOCH } ]
    if {$static::debug == 1} { log local0.info "Difference between Now and Start Epoch is $DIFFERENCE" }
    
    if { $DIFFERENCE < $DURATION } {
     Within maintenance window
    if {$static::debug == 1} { log local0.info "In Maintenance window - Returning holding page." }
    if { [HTTP::uri] ends_with "logo-act.png" } {
    if {$static::debug == 1} { log local0.info "Returning Logo..." }
    HTTP::respond 200 content [b64decode [class element -value 0 act_logo_png]] "Content-Type" "image/png"
    } else {
    if {$static::debug == 1} { log local0.info "Returning html..." }
    HTTP::respond 200 content [class element -value 0 offline_page_html] "Content-Type" "text/html" 
    }
    
     Set Maintenance mode global variable. 
    set ::MAINTENANCE_MODE 1
    
    } elseif { $DIFFERENCE > $DURATION } {
     Outside of Maintenance window
    if {$static::debug == 1} { log local0.info "Outside of Maintenance window. Unsetting Maintenance Mode." }
    
     Unset maintenance mode flag for good measure
    set ::MAINTENANCE_MODE 0
    
     Remove session table entry. 
    table delete -subtable $static::SUBTABLE $PROFILE_NAME
    }
    }
    
    } else {
    
         Not in maintenance window, so allow connection to continue to application.
    if {$static::debug == 1} { log local0.info "Not In Maintenance window - Continuing." }
    
     Ensure Maintenance Mode global variable is unset. 
    set ::MAINTENANCE_MODE 0
    
        }
        }
      }
    }
  • I haven't read through the whole rule, but one simple optimization would be to use a subtable instead of a global variable for maintenance_mode. A subtable will be CMP compatible whereas a global variable will pin the iRule to running only on the first TMM instance.

     

     

    See the CMP page for details:

     

    http://devcentral.f5.com/wiki/iRules.CMPCompatibility.ashx

     

     

    Aaron
  • spark_86682's avatar
    spark_86682
    Historic F5 Account
    The ::MAINTENANCE_MODE variable appears to never actually be read, only written to. I think it can be removed entirely. But that looks like a pretty good adaptation of that iRule! Nice job!
  • Hoolio, cheers for the pointer. Will see if I can work up a very simple check for the other rules to use...

     

     

    Spark, you're right that it never gets read in this rule. However its the first thing to be read in the other rules... will post some sample code up later...

     

     

    Gav
  • Ok, I've gone back and simplified the rule so that it can be easily checked from other rules...

     

     

    New rule looks like:

     

    Edit: Posted in next reply as formatting when strange...

     

     

    The framework for other rules to check for the maintenance window is:

     

    when RULE_INIT {
     Debug
    set static::debug 1
    set static::MAINTENANCE "maintenance_window"
    }
    
    when HTTP_REQUEST priority 300 {
    
    if { [table lookup -subtable $static::MAINTENANCE [virtual name]] != "" } {
     In Maintenance mode. Log and return. 
    if { $static::debug == 1 } {log local0.info "In Maintenance mode. Returning..." }
    return
    } else {
     Not in Maintenance mode. Continuing...
    if { $static::debug == 1 } {log local0.info "Not In Maintenance mode. Continuing..." }
    
    Do stuff here...
    }
    }

     

     

    Again, comments welcome...

     

     

    I'll also look at getting this submitted to Code Share at some point over the next couple of days.

     

     

    Regards

     

    Gavin

     

  • iRule:

    
    when RULE_INIT {
    
    set static::SUBTABLE "maintenance_window"
    set static::debug 1
      
    }
    
    
    when HTTP_REQUEST priority 10 {
      
    set VS_NAME [virtual name]
    set TITLE "[substr $VS_NAME 8] iRule Maintenance Window Control"
      
    if {$static::debug == 1} { log local0.info "VS Name: $VS_NAME Title: $TITLE" }
    
     Look for embedded commands to control the maintenance window.
    
    switch -glob [string tolower [HTTP::uri]] {
    "/enable*" {
    
     Extract the maintenance window day, start date time, and duration from the URI. 
     If not there, return an error message.
    
    set params [split [HTTP::uri] "/"]
    if { [llength $params] < 4 } {
    HTTP::respond 200 content "
    $TITLE
    Usage: http://[HTTP::host]/enable/start_date_time/duration
    Start Date Time must be of the format yyyy-mm-dd hh24:mi:ss
    Duration is in Hours
    
    "
    } else {
    
     Assign the values to variables. 
    
    set START_VALUE [string map {"%20" " "} [lindex $params 2] ]
    set DURATION_HOURS [lindex $params 3]
    if {$static::debug == 1} { log local0.info "Start Value = $START_VALUE, Duration = $DURATION_HOURS." }
    
     Process input
    set START_EPOCH [clock scan $START_VALUE ]
    if {$static::debug == 1} { log local0.info "Start EPOCH = $START_EPOCH" }
    
    set DURATION_SECS [expr {$DURATION_HOURS*60*60}]
    if {$static::debug == 1} { log local0.info "Duration = $DURATION_SECS" }
    
     Add value to subtables
    table set -subtable $static::SUBTABLE $VS_NAME $START_EPOCH 0 $DURATION_SECS
    
    HTTP::respond 200 content "
      $TITLE
      Maintenance Window enabled
      "
    }
        }
        "/disable" {
    
     By removing the session table entries, the maintenance is removed.
    if {$static::debug == 1} { log local0.info "Removing set Maintenance window..." }
    table delete -subtable $static::SUBTABLE $VS_NAME
      
    HTTP::respond 200 content "
    $TITLE
    Maintenance Window disabled
    "
    }
        "/check" {
    
     Return the current time and the current settings for the maintenance window
    set START_EPOCH [table lookup -subtable $static::SUBTABLE $VS_NAME]
     Set Common variables. 
    set NOW_EPOCH [clock seconds]
    set NOW_DTS [clock format $NOW_EPOCH ]
    
    if { $START_EPOCH != "" } {
    set START_DTS [clock format $START_EPOCH]
    set DURATION [table lifetime -subtable $static::SUBTABLE $VS_NAME]
    set DIFFERENCE [expr { $NOW_EPOCH - $START_EPOCH } ]
    set RESPONSE "$START_DTS for [expr {$DURATION/60/60}] hours"
    
     Debug Logging
    if {$static::debug == 1} { 
    log local0.info "Maintenance window set..."
    log local0.info "Table values: Start EPOCH - $START_EPOCH Duration - $DURATION" 
    log local0.info "Start DTS = $START_DTS Date Time now = $NOW_EPOCH" 
    log local0.info "Difference = $DIFFERENCE"
    }
    } else {
    set START_DTS "No Maintenance Window set"
    set RESPONSE $START_DTS
    
     Debug Logging
    if {$static::debug == 1} { log local0.info "No Maintenance Window set..." }
    }
    
    HTTP::respond 200 content "
    $TITLE
    Current Date and time: $NOW_DTS
    Maintenance Window: $RESPONSE
    "
        }
        "/usage" {
    
             Usage instructions for the commands
    
            HTTP::respond 200 content "
    $TITLE
    Usage: http://[HTTP::host]/\[command\]
    
    /enable/start_date_time(yyyy-mm-dd hh24:mm)/duration(h) - enable maintenance window.
    /disable - disable maintenance window.
    /check - check if in maintenance window.
    /usage - display this usage message
    
    "
        }
        default {
    
     no secret command entered, so check the maintenance window
    
     Get the values from the relevant session subtable.
    set START_EPOCH [table lookup -subtable $static::SUBTABLE $VS_NAME]
    set DURATION [table lifetime -subtable $static::SUBTABLE $VS_NAME]
    
     Debug logging 
    if {$static::debug == 1} { log local0.info "Table values: Start EPOCH - $START_EPOCH Duration - $DURATION" }
    
     Check if start_epoch is non-null. 
    if { $START_EPOCH != "" } {
    
     Use the TCL "clock" command to get the current time as epoch.
    set NOW_EPOCH [clock seconds]
    if {$static::debug == 1} { log local0.info "Current EPOCH Time = $NOW_EPOCH" }
    
     Check NOW against Scheduled start
    if { $NOW_EPOCH < $START_EPOCH } {
     Not yet in maintenance, therefore return for other processing
    if {$static::debug == 1} { log local0.info "Before scheduled maintenance window. Returning to continue other rules..." }
    return
    
    } else {
     Now must be greater than Start Epoch value, therefore in maintenance or after maintenance
    set DIFFERENCE [expr { $NOW_EPOCH - $START_EPOCH } ]
    if {$static::debug == 1} { log local0.info "Difference between Now and Start Epoch is $DIFFERENCE" }
    
    if { $DIFFERENCE <= $DURATION } {
     Within maintenance window
    if {$static::debug == 1} { log local0.info "In Maintenance window - Returning holding page." }
    if { [HTTP::uri] ends_with "logo-act.png" } {
    if {$static::debug == 1} { log local0.info "Returning Logo..." }
    HTTP::respond 200 content [b64decode [class element -value 0 act_logo_png]] "Content-Type" "image/png"
    } else {
    if {$static::debug == 1} { log local0.info "Returning html..." }
    HTTP::respond 200 content [class element -value 0 offline_page_html] "Content-Type" "text/html" 
    }
    
    } else {
     Outside of Maintenance window
    if {$static::debug == 1} { log local0.info "Outside of Maintenance window. Unsetting Maintenance Mode." }
    
     Remove session table entry. 
    table delete -subtable $static::SUBTABLE $VS_NAME
    }
    }
    
    } else {
    
     Not in maintenance window, so allow connection to continue to application.
    if {$static::debug == 1} { log local0.info "Not In Maintenance window - Continuing." }
    
    }
    }
      }
    }
    
  • FYI - Have posted it on CodeShare...

     

     

    Link is: http://devcentral.f5.com/wiki/iRules.LTM-Dynamic-Maintenance-using-Session-Table.ashx?NoRedirect=1&NS=iRules