Forum Discussion

AppleBee_108607's avatar
AppleBee_108607
Historic F5 Account
Nov 02, 2010

CMP compatible strict Round Robin

Hi,

 

 

In addition to my another post, I have to urgently write an iRule to facilitate strict Round Robin event if the Virtual is CMPenabled.

 

 

I wrote code as follows but still have two difficulities:

 

 

(1) Initializing data in table

 

It seems "table" command cannot be used in RULE_INIT event.

 

 

(2) I'm not clear how to specify variable type: numeric or string (Ooops..)

 

and having difficulty in calculating variables.

 

 

(3) Want to set general variables in RULE_INIT

 

--> Probably I can just use "static::xxx"

 

 

Excuse me for newbee question.

 

 

--------------------------------

 

when RULE_INIT {

 

}

 

 

when CLIENT_ACCEPTED {

 

set poolname "test-pool"

 

set num_pool_member 4

 

set pool_port 80

 

set ip_pm0 "10.100.11.11"

 

set ip_pm1 "10.100.11.12"

 

set ip_pm2 "10.100.12.11"

 

set ip_pm3 "10.100.12.12"

 

 

get next poolmember's index

 

set last_mod [table lookup -subtable "rr_table" "last_mod"]

 

 

---- Here, not sure how to set correct data tye ------

 

set new_mod [ expr { expr { $last_mod + 1 } % $num_pool_member} ]

 

table set -subtable "rr_table" "last_mod" $new_mod

 

 

-----

 

Here I want to check the validily of the variable as "table" cannot be initialized in RULE_INIT

 

I also want to check if it's not empty but non-numeric value is set.

 

if { $last_mod == "" } {

 

table set -subtable "rr_table" "last_mod" 0 indefinite indefinite

 

set new_mod 1

 

}

 

 

switch pool member based on the index

 

switch $new_mod {

 

0 {

 

if { [LB::status pool $poolname member $ip_pm0 $pool_port up] } {

 

pool $::poolname member $ip_pm0 $pool_port

 

log local0.info "LBed to pm 0"

 

} else {

 

continue

 

}

 

}

 

1 {

 

if { [LB::status pool $poolname member $ip_pm1 $pool_port up] } {

 

pool $poolname member $ip_pm1 $pool_port

 

log local0.info "LBed to pm 1"

 

} else {

 

continue

 

}

 

}

 

2 {

 

if { [LB::status pool $poolname member $ip_pm2 $pool_port up] } {

 

pool $::poolname member $ip_pm2 $pool_port

 

log local0.info "LBed to pm 2"

 

} else {

 

continue

 

}

 

}

 

3 {

 

if { [LB::status pool $::poolname member $ip_pm3 $pool_port up] } {

 

pool $::poolname member $ip_pm3 $pool_port

 

log local0.info "LBed to pm 3"

 

} else {

 

continue

 

}

 

}

 

default {

 

log "no pool to assign for Virtual Server XX"

 

}

 

}

 

}

 

--------------------------------
  • AppleBee_108607's avatar
    AppleBee_108607
    Historic F5 Account
    Wheeww. I finally got it working, referring to an iRule from my colleague, which I guess comes from somewhere in DevCentral.

     

     

    This one is much nicer coz you have to only modify the pool name.

     

     

    I hope "initial setup" section can be moved to other EVENTs that is triggered only once after the iRule is loaded. And periodically update the list of pool member with "after" command or something.

     

     

    ---------------------------------------------------------

     

    when RULE_INIT {

     

    set pool name

     

    set static::poolname "test-pool"

     

    }

     

     

    when CLIENT_ACCEPTED {

     

    initial setup

     

    $num_pm: number of the pool member

     

    set num_pm [members $static::poolname]

     

    set pm_list [members -list $static::poolname]

     

    table add -subtable tbl_rr_$static::poolname "last_mod" 0

     

     

    per connection process

     

    choose index of the pool member to LB to

     

    set last_mod [table lookup -subtable tbl_rr_$static::poolname "last_mod"]

     

    set new_mod [expr {[expr {$last_mod + 1}] % $num_pm}]

     

    table set -subtable tbl_rr_$static::poolname "last_mod" $new_mod

     

     

    check pool availability

     

    if { [active_members $static::poolname] < 1 } {

     

    if there is no active member, reject

     

    reject

     

    }

     

     

    round robin LB

     

    set pm [lindex $pm_list $new_mod]

     

    set mon_stat [LB::status pool $static::poolname member [lindex $pm 0] [lindex $pm 1]]

     

    if {$mon_stat == "up"} {

     

    pool $static::poolname member [lindex $pm 0] [lindex $pm 1]

     

    } else {

     

    for {set i 1} {$i < $num_pm} {incr i} {

     

    set j [expr {[expr {$i + $new_mod}] % $num_pm}]

     

    set pm [lindex $pm_list $j]

     

    set mon_stat [LB::status pool $static::poolname member [lindex $pm 0] [lindex $pm 1]]

     

    if {$mon_stat == "up"} {

     

    pool $static::poolname member [lindex $pm 0] [lindex $pm 1]

     

    table set -subtable tbl_rr_$static::poolname "last_mod" $J

     

    break

     

    }

     

    }

     

    }

     

    }

     

    ----------------------------------------------------------------
  • spark_86682's avatar
    spark_86682
    Historic F5 Account
    To answer your questions:

     

    You can't access the session table in RULE_INIT or any other global event, that's true. However, don't forget that entries in the session table deliberately expire, so even if you could initialize them in RULE_INIT, they may be gone later, and your iRule has to cope with that (that's one reason there are so many variations on things like the set command). Fortunately, the table commands are written with that in mind as well, and tend to Do The Right Thing in the case where there is no existing entry. To give one example (that will become relevant shortly), if you do something like increment a key that doesn't exist, it automatically starts it as 0.

     

    I don't think there is a way to explicitly say that a value is a string or a number; Tcl generally converts automatically when necessary. However, I can't see where you're running into this problem. Could you elaborate a bit?

     

    Yes, to set variables in RULE_INIT, just use "set static::myvarname myvalue".

     

    As for your round-robin iRule, I think that you're waaaaaay overthinking this. How about something like:
    when CLIENT_ACCEPTED {
      set poolname "test-pool"
    
      if { [active_members $poolname] < 1 } {
         No active pool members; reset client
        reject
        return
      }
    
      set count [members $poolname]
      set attempt 0
      while { $attempt < $count } {
        set num [table incr "round-robin:$poolname"]
        set num [expr $num % $count]
        set mbr [lindex [members -list $poolname]]
        set mbr_ip [lindex $mbr 0]
        set mbr_port [lindex $mbr 1]
        if { [LB::status pool $poolname member $mbr_ip $mbr_port up } {
          pool $poolname member $mbr_ip $mbr_port
          return
        }
        incr attempt
      }
    }
    
    Again, not actually tested or syntax-checked code, and written for clarity, not compactness or performance.
  • spark_86682's avatar
    spark_86682
    Historic F5 Account
    This rule is good, but the session table is a shared global structure, and this iRule does what's referred to as a "read-modify-write" to that data, which is bad. The three lines are:

     

        set last_mod [table lookup -subtable tbl_rr_$static::poolname "last_mod"]
        set new_mod [expr {[expr {$last_mod + 1}] % $num_pm}]
        table set -subtable tbl_rr_$static::poolname "last_mod" $new_mod
    
    The first one reads some data from the session table, the second one modifies that data, and the third one writes it back. Hence, "read-modify-write". Consider what would happen if two different connections came in at the same time and both executed this code. They would both read in a value (say, 3), both modify it (so new_mod would be 4), and both write it back out. So the two connections would both use the 4th pool member, which isn't what you want. In my other post, I use "table incr", which does an atomic increment, so this couldn't happen, since there's no read-modify-write happening. If two connections do an increment at the same time, one incrment command will actually be serviced first, so the "table incr" command will never return the same value to the two connections, so they'll always go to different pool members. Make sense?
  • AppleBee_108607's avatar
    AppleBee_108607
    Historic F5 Account
    Hi Spark,

     

    Thanks aaa lllooott!

     

     

    It's amazing how you can write these logic into this compact code.

     

     

    I just made small syntax-wise modification and it worked off the shelf. I will use this at my customer. Yep, and I was also aware of the implication of read-modify-write time gap, but just didn't know how to tackle it.

     

     

    Once again, thanks a lot!

     

     

    ----------------------------------------------------

     

    when CLIENT_ACCEPTED {

     

    set poolname "test-pool"

     

     

    if { [active_members $poolname] < 1 } {

     

    No active pool members; reset client

     

    reject

     

    return

     

    }

     

     

    set count [members $poolname]

     

    set attempt 0

     

    while { $attempt < $count } {

     

    set num [table incr "round-robin:$poolname"]

     

    set num [expr {$num % $count}]

     

    set mbr [lindex [members -list $poolname] $num]

     

    set mbr_ip [lindex $mbr 0]

     

    set mbr_port [lindex $mbr 1]

     

    if { [LB::status pool $poolname member $mbr_ip $mbr_port up] } {

     

    pool $poolname member $mbr_ip $mbr_port

     

    return

     

    }

     

    incr attempt

     

    }

     

    }

     

    ----------------------------------------------------