Forum Discussion

Jeremy_Rosenber's avatar
Jeremy_Rosenber
Icon for Nimbostratus rankNimbostratus
Jun 05, 2014

Code review: Http conditionally reselect for active connections based on persistence when member down or disabled

I am looking for a concept/code review or alternative suggestions if I'm going about this all wrong.

 

What we are trying to do is be able to maintain servers / release web application updates, killing as few users as possible.

 

We use source address persistence on the http vs. Pool is selected based on request uri using an existing rule. Each pool has a monitor that hits a keep alive page with just a receive string defined - generally 'OK3' or some sort. We do not use oneconnect. Big ip is 10.2.4 HF7.

 

Trouble is even after we take out a keep alive page and the member is marked down, requests on already-active connections are still going to the down server. Some of these requests are from external browsers that are using http keep alive to maintain connection. As long as they are not idle, their connections persist. Other requests are from other web applications which also pool their connections and keep them open. New connections are re-balanced, as expected, but this means any requests that rely on persistence for web server state - e.g. images stored locally - break.

 

What I would like to happen is that when we are going to take a member out for maintenance, to be able to drain the requests by setting the Disable string on the monitor. Then at the request-level when disabled - i.e. even for active connections - to reselect and update persistence, but only if I know the particular request does not require persistence, based on uri. Otherwise, keep the connection on the same member. We'll monitor the connections at this point while it's draining. When everyone is off, or we've waited long enough, bring it all the way down and do maintenance.

 

I wrote the following to do this.

 

when HTTP_REQUEST {
  set lb_server_addr [LB::server addr]

  if { $lb_server_addr != "" } {
    set lb_server_pool [LB::server pool]
    set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr [LB::server port]]

    if { $lb_server_status == "down" || $lb_server_status == "session_disabled" } {
       Reselect active connections when pool member is out
       Only do this if there are other active members available..
      if { [active_members $lb_server_pool] > 0 } {
         Only if the client is NOT requesting stateful things
         Switch on path after partner, e.g. for /tenant/foo/bar.aspx, this switches on /foo/bar.aspx
        switch -glob [URI::path [HTTP::path] 2] {
          "/ThisPathNeedsPersistence/*" -
          "/This/Path/Needs/Persistence" {
             Do nothing - allow request through
          }
          default {
            LB::detach

             Delete any persistence record so we get a new one.  The record is automatically
             invalidated when server is DOWN, but not when DISABLED
            if { [persist lookup source_addr [IP::client_addr] node] == $lb_server_addr } {
              persist delete source_addr [IP::client_addr]
            }

            pool $lb_server_pool
          }
        }
      }
    }
  }
}

It seems to work in testing, but is it ok? Are there any weird conditions you can think of that would break this that I should account for? Or alternatives? Or is it right in general but I'm using something stupidly?

 

I did try Action on Service Down Reselect, but it never seems to do anything when the node is marked down - or at least not consistently. If it did work though, one problem is I assume in-flight requests may be lost. Also it would give us no opportunity to drain the connections with persistence requirements, breaking those, too.

 

Let me know if I can clarify anything.

 

10 Replies

  • Hi,

     

    Please have a look at tis solution to check if this is the behaviour you would need. http://support.f5.com/kb/en-us/solutions/public/13000/300/sol13310.html Best regards, Mario

     

    • Jeremy_Rosenber's avatar
      Jeremy_Rosenber
      Icon for Nimbostratus rankNimbostratus
      Disabled does not affect persistence clients (all of ours). Forced offline does not migrate existing connections it only prevents new connections. Our problem is with long running active connections. Also our production big ip is managed I don't have access to the options you linked in prod unless I schedule it w provider.
  • Hi!

     

    What you might want is to set the action on service down to reject instead. First you drain the connections with the disable string and then it will force the client to restart the session and thus get a new persistence record when the object is marked as down.

     

    /Patrik

     

  • Hi!

    Making this a reply as the syntax for comments is horrible.

    I tried the rule and it seems to do what you want it to do at first glance.

    I am curious though about what would happen to the persistent connections if you'd implement this rule. The detach command would close the connection with the server, but the client would be unknowing.

    Unless you're using SNAT today letting the load balancer handle the server connection for the client, I imagine this would result in the client sending a request over an open TCP connection ending up at a new server on which the connection is not established. My guess is that this would result in an TCP connection reset forcing the client to open a new session. Could be worth investigating.

    A humble suggestion as to the rule syntax would be to replace the switch -glob with a data group list instead:

    when HTTP_REQUEST {
        set lb_server_addr [LB::server addr]
    
        if { $lb_server_addr != "" } {
            set lb_server_pool [LB::server pool]
            set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr [LB::server port]]
    
            if { $lb_server_status == "down" || $lb_server_status == "session_disabled" } {
    
                 Reselect active connections when pool member is out
                 Only do this if there are other active members available..
                if { [active_members $lb_server_pool] > 0 } {
                     Only if the client is NOT requesting stateful things
                     Switch on path after partner, e.g. for /tenant/foo/bar.aspx, this switches on /foo/bar.aspx
                    if { ![class search persistencepaths starts_with [string tolower [HTTP::uri]]] } {
    
                        LB::detach
    
                         Delete any persistence record so we get a new one.  The record is automatically
                         invalidated when server is DOWN, but not when DISABLED
                        if { [persist lookup source_addr [IP::client_addr] node] == $lb_server_addr } {
                            persist delete source_addr [IP::client_addr]
                        }
    
                        pool $lb_server_pool
                    }
                }
            }
        }
    }
    

    /Patrik

  • Here's an updated rule which seems better than the original. This doesn't require the 'disable' state. It is better at dealing with new and existing connections, consistently forcing requests where persistence is known to be needed to the down server while redirecting to an active member for other requests. It also doesn't muck with the persistence record, just disables on the connection with 'persist none'. Finally, it restores the original persistence node when the down server comes back up.

    Just posting this FYI. I'll be testing it more, but seems much better so far. Not sure the performance impact.

    when HTTP_REQUEST {
      set lb_server_pool [LB::server pool]
      set lb_server_addr [LB::server addr]
    
      if { $lb_server_pool != "" && $lb_server_addr != "" } {
        if { !([info exists drain_pool]) } {
          set lb_server_port [LB::server port]
          set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr $lb_server_port]
    
          if { $lb_server_status != "up" } {
            set drain_pool $lb_server_pool
            set drain_addr $lb_server_addr
            set drain_port $lb_server_port
          }
        }
    
        if { [info exists drain_pool] } {
          LB::detach
          persist none
    
          set drain_status [LB::status pool $drain_pool member $drain_addr $drain_port]
    
          if { $drain_status == "up" } {
            if { $drain_pool == $lb_server_pool } {
               pool $drain_pool member $drain_addr $drain_port
               persist source_addr
            }
    
            unset drain_pool
            unset drain_addr
            unset drain_port
          } elseif { $drain_pool == $lb_server_pool } {
    
            switch -glob "[URI::path [HTTP::path] 2][URI::basename [HTTP::uri]]" {
              "/ThisPathNeedsPersistence/*" -
              "/This/Path/Needs/Persistence" {
                pool $drain_pool member $drain_addr $drain_port
              }
              default {
                pool $drain_pool
              }
            }
          }
        }
      }
    }
    
    • Kevin_Davies_40's avatar
      Kevin_Davies_40
      Icon for Nacreous rankNacreous
      Can you add comments as it is difficult to follow in which scenarios parts of the iRule are used.
    • Jeremy_Rosenber's avatar
      Jeremy_Rosenber
      Icon for Nimbostratus rankNimbostratus
      If anyone knows how to delete this reply... I added comments in another one.
  • Here's an updated rule which seems better than the original. This doesn't require the 'disable' state. It is better at dealing with new and existing connections, consistently forcing requests where persistence is known to be needed to the down server while redirecting to an active member for other requests. It also doesn't muck with the persistence record, just disables on the connection with 'persist none'. Finally, it restores the original persistence node when the down server comes back up.

    Just posting this FYI. I'll be testing it more, but seems much better so far. Not sure the performance impact.

    when HTTP_REQUEST {
      set lb_server_pool [LB::server pool]
      set lb_server_addr [LB::server addr]
    
      if { $lb_server_pool != "" && $lb_server_addr != "" } {
         If not yet draining, check if we should start
        if { !([info exists drain_pool]) } {
          set lb_server_port [LB::server port]
          set lb_server_status [LB::status pool $lb_server_pool member $lb_server_addr $lb_server_port]
    
           Start draining member if it is not up
          if { $lb_server_status != "up" } {
            set drain_pool $lb_server_pool
            set drain_addr $lb_server_addr
            set drain_port $lb_server_port
          }
        }
    
         If draining, we'll make a LB decision each request
        if { [info exists drain_pool] } {
          LB::detach
    
           Ignore persistence as we're basically managing our own.  We'll restore it when we're back up.  If we didn't ignore it here, then our commands to select an active member will be ignored in favor of the persisted member.
          persist none
    
          set drain_status [LB::status pool $drain_pool member $drain_addr $drain_port]
    
           When draining member is back up, stop tracking it
          if { $drain_status == "up" } {
             If we're going to that pool, go back to the node we were draining and reset persistence to it
            if { $drain_pool == $lb_server_pool } {
               pool $drain_pool member $drain_addr $drain_port
               persist source_addr
            }
    
            unset drain_pool
            unset drain_addr
            unset drain_port
          } elseif { $drain_pool == $lb_server_pool } {
             At this point we're draining a member, it's still down, and
             we are indeed going to that pool.
             Go to the draining member only for requests we know persistence is necessity.
             Otherwise, choose an active member instead.
            switch -glob "[URI::path [HTTP::path] 2][URI::basename [HTTP::uri]]" {
              "/ThisPathNeedsPersistence/*" -
              "/This/Path/Needs/Persistence" {
                pool $drain_pool member $drain_addr $drain_port
              }
              default {
                pool $drain_pool
              }
            }
          }
        }
      }
    }
    
    • Jeremy_Rosenber's avatar
      Jeremy_Rosenber
      Icon for Nimbostratus rankNimbostratus
      Added commented version at request of Kevin Davies. Sorry about that. Had to add as separate reply it wouldn't let me edit. (Save button no worky)
    • Patrik_Jonsson's avatar
      Patrik_Jonsson
      Icon for MVP rankMVP
      I'd change to case insensitive comparison by using string tolower, and change the "-glob" part. ;) Otherwise it looks ok to me. /Patrik