Horizontal Scale BIG-IP Device Service Cluster (DSC) with Software Defined Networking (SDN) enabled Hardware.

Problem this snippet solves:

Using iApp/iRules technology, this Proof of Concept (POC) iApp shows how to integrate the F5 TMOS programmable platform with a Software Defined Networking (SDN) switch.

Code :

#TMSH-VERSION: 11.6.0

cli admin-partitions {
    update-partition Common
}
sys application template /Common/f5.sdn_scale {
    actions {
        definition {
            html-help {
            }
            implementation {
set ::controller_name [ format "/Common/%s_controller" $tmsh::app_name ]            
set ::openflow_controller {
# IRULE_OPENFLOW_CONTROLLER
# OpenFlow 1.3 will be supported in the next version

}

set ::json_controller {
# IRULE_JSON_CONTROLLER
proc json_value { list } {
    set key [ lindex $list 0]
    set value [lindex $list 1]
    return "\"$key\":\"$value\""
}
proc json_nest { key } {
    return "\"$key\":\{"
}

proc json_array { key } {
    return "\"$key\":\["
}


when HTTP_REQUEST {
    set cluster_members [ class lookup "clusterMembers" sdn_scale_data ]
    set monitor_vlan [class lookup "monitorVlan" sdn_scale_data ]
    set dag_rule [class startsearch sdn_scale_rules]
    set counter_rule 0
    set data "\{"
    append data [ call json_value { type F5SdnScale } ]
    append data ","
    append data [ call json_value { version 2 } ]
    append data ","
    append data "\"monitorVlan\":\"$monitor_vlan\""
    append data ","
    append data "\"clusterMembers\":$cluster_members"
    append data ","
    append data [call json_array rules ]
    set delim ""
    while {[class anymore sdn_scale_rules $dag_rule]}{
        set entry [class nextelement sdn_scale_rules $dag_rule]
        log local0. "entry is $entry"
        append data "$delim\{"
        set id ""
        lappend id id
        lappend id [lindex $entry 0]
        append data [call json_value $id ]
        append data ","
        append data [ call json_nest [lindex [lindex [lindex $entry 1] 0] 0] ]
        append data [call json_value [ lindex [lindex [lindex [lindex $entry 1] 0] 1] 0] ]
        append data ","
        append data [call json_value [ lindex [lindex [lindex [lindex $entry 1] 0] 1] 1] ]
        append data "\}"
        append data ","
        append data [ call json_nest [lindex [lindex [lindex $entry 1] 0] 2] ]
        append data [call json_value [ lindex [lindex [lindex [lindex $entry 1] 0] 3] 0] ]
        append data ","
        append data [call json_value [ lindex [lindex [lindex [lindex $entry 1] 0] 3] 1] ]
        append data "\}"
        append data "\}"
        set delim ","
    }
    append data "\], \"commit\":\"true\"\}"
    class donesearch sdn_scale_rules $dag_rule
    HTTP::respond 200 content $data
}

}

proc get_net_id { net } {
     foreach { ip mask  } [ split $net / ] break
     set zeros [expr 32 - ${mask}]
     set ones ${mask}
     set bm [string repeat 1 $ones]
     append bm [string repeat 0 $zeros]
     binary scan [binary format B32 $bm] H8 maskh
     set octets [split $ip .]
     binary scan [binary format c4 $octets] H8 iph
     return $iph$maskh
}


proc uuid {prefix net vlan} {
     set seed [ get_net_id $net ]
     set vlanseed [ format "%05d" $vlan ]
     append uuid $prefix
     append uuid -[string range $vlanseed 0 4]
     append uuid [string range $seed 0 3]
     append uuid -[string range $seed 4 15]

     return $uuid
}

proc getConfPrefix {virtual type} {
    set prefix none
    if { $type == "incr" } { 
        set prefix [ readConfPrefix $virtual]
    }
    if { $prefix == "none" } {
        set prefix [ genConfPrefix ]
    }
    storeConfPrefix $virtual $prefix
    return $prefix
}

proc readConfPrefix { virtual } {
    set prefix [lindex \
                    [lindex \
                        [lindex \
                            [ tmsh::list /ltm virtual $virtual metadata ] \
                        3 ] \
                    1 ] \
                0]
    return $prefix
}

proc genConfPrefix {} {
    set prefix [format %2.2x [clock seconds]]
    append prefix -[string range [format %2.2x [clock clicks]] 0 3]
    append prefix -[string range [format %2.2x [clock clicks]] 2 5]
    append prefix -[string range [format %2.2x [clock clicks]] 4 8]
    return $prefix
}

proc storeConfPrefix {virtual prefix } {
    tmsh::create /ltm virtual $virtual metadata\
        replace-all-with  \{ $prefix \}
}
 
proc mac_incr { mac } {
      set bytes [ split $mac ":" ]
      set pos 5
      set next_byte 1
      while { $next_byte && [expr $pos + 1]} {
         set next_byte 0
         set byte [lindex $bytes $pos]
         scan $byte "%x" dec
         incr dec
         set byte [format "%x" $dec]
         if { $byte == 100 } {
            set bytes [lreplace $bytes $pos $pos "00"]
            set next_byte 1
            incr pos -1
         } elseif { [string length $byte] == 1 } {
            set bytes [lreplace $bytes $pos $pos "0$byte"]
            set next_byte 0
         } else {
            set bytes [lreplace $bytes $pos $pos "$byte"]
            set next_byte 0
         }
      }
      set mac ""
      for {set pos 0 } {$pos < 6} {incr pos} {
         if { $pos == 0 } {
            set mac "$mac[lindex $bytes $pos]"
         } else {
            set mac "$mac:[lindex $bytes $pos]"
         }
      }
   return "$mac"
}

proc hex_to_ip {hex} {
    set bin [binary format I [expr {$hex}]]
    binary scan $bin c4 octets
    foreach octet $octets {
        lappend result [expr {$octet & 0xFF}]
    }
    return [join $result .]
}

proc ip_to_hex {ip } {
    set octets [split $ip .]
    binary scan [binary format c4 $octets] H8 x
    return 0x$x
}

proc net_incr {network counter } {
    set address [ lindex [ split $network / ] 0 ]
    set mask [ lindex [ split $network / ] 1]
    return   [ hex_to_ip [ expr { [ip_to_hex $address ] + $counter } ]]/$mask
}

proc ip_incr {address counter} {
    return   [ hex_to_ip [ expr { [ip_to_hex $address ] + $counter } ]]
}

proc split_net { net depth } {
    set depth [ expr { $depth -1 } ]
    set cdir [ lindex [split $net "/"] 1]
    set net [ lindex [split $net "/"] 0]
    set cdir [ expr { $cdir + 1} ]
    set lower "$net/$cdir"
    set higher [hex_to_ip [ expr { [ ip_to_hex $net ]  | (2 << (31 -$cdir )) }]]
    set higher "$higher/$cdir"
    if { $depth > 0 && $cdir < 28 } {
        set lower [ split_net $lower $depth]
        set higher [ split_net $higher $depth]
    }
    return  "$lower $higher"
}

proc gen_dag {node_mac} {
    set prefix [getConfPrefix $::controller_name $::disaggregation__update__type] 
    foreach row $::disaggregation__subscriber {
        array set columns [lindex $row 0]
        lappend subnets $columns(net)
    }
    foreach row $::networking__net {
    array set network [lindex $row 0]
        # splitting up each of the subscriber networks as per disaggregation depth
        
        foreach subnet $subnets {
            set counter 0
            
            foreach net [split_net $subnet $::disaggregation__depth] {
                incr counter
                
                # node1 is the default node, no DAG needed
                set mac [ lindex $node_mac [expr { $counter % [ llength  $node_mac ] } ]]
                if { !(( $mac == [lindex $node_mac 0] ) \
                    && $::disaggregation__optimiser ) } {
                    
                    if { $network(dag) != "ha" } {
                    set vlan $network(vlan_vid)
                    set dump "{match {{ vlan_vid $vlan } \
                            { $network(dag) $net }} action \
                            {{ dl_src $mac } { port NORMAL }}}"
                    tmsh::create /ltm data-group \
                        internal /Common/sdn_scale_rules records \
                            add \{ [ uuid $prefix $net $vlan ] \
                                \{  data \"$dump\" \} \} type string
                    }
                }
            }
        }
    }
}

proc gen_self { traffic_group counter } {
    set clusterMember ""
    set monitorVlan ""
    foreach row $::networking__net {
        array set network [lindex $row 0]
        set vlan_name [ string replace $network(vlan) 0 7 ]
        
        set address [ net_incr $network(address) $counter ]
        
        if { $network(dag) == "ha" } {
            foreach { ip mask  } [ split $address / ] break
            set clusterMember $ip 
            set monitorVlan $network(vlan_vid)
        }

        tmsh::create /net self /Common/$traffic_group"_"$vlan_name \
            \{ address $address \
            traffic-group $traffic_group vlan $network(vlan) allow-service all \}
    }

    return $clusterMember/$monitorVlan
}

proc gen_net { base_mac } {
    set counter 1
    set clusterMembers ""
    set dl_addresses ""
    foreach row $::cluster__active {
        array set columns [lindex $row 0]

        #generating the traffic groups
        set traffic_group sdn_scale_node_$counter
        tmsh::create /cm traffic-group /Common/$traffic_group \
            \{ ha-order \{ $columns(nodes) $::cluster__standby__device \} mac $base_mac\}

        set clusterMember [ gen_self $traffic_group $counter ]
        foreach { clusterMemberIp monitorVlan  } [ split $clusterMember / ] break 
        lappend clusterMembers $clusterMemberIp
        lappend dl_addresses $base_mac
        set base_mac [ mac_incr $base_mac ]
        incr counter
    }
    storeClusterData $clusterMembers $monitorVlan
    return $dl_addresses
}

proc storeClusterData {clusterMembers monitorVlan} {
    set clusterMembersJSON ""
    set prefix "\["
    set suffix "\]"
    set delim ""
    
    append clusterMembersJSON $prefix

    foreach clusterMember $clusterMembers {
        append clusterMembersJSON $delim\"\\\"$clusterMember\"\\\"
        set delim ","
    }
    append clusterMembersJSON $suffix
    tmsh::create /ltm data-group \
        internal /Common/sdn_scale_data records \
        add \{ clusterMembers \
            \{  data \"$clusterMembersJSON\" \} \} type string

    tmsh::create /ltm data-group \
        internal /Common/sdn_scale_data records \
        add \{ monitorVlan \
            \{  data \"$monitorVlan\" \} \} type string

    puts $clusterMembersJSON
}

proc gen_contr {} {
    set name [ format "/Common/%s_controller" $tmsh::app_name ]
    switch $::controller__instance__type {
        "openflow" {
        # currently not implemented
        }
        "json" {
            tmsh::create ltm rule $name \
            [tmsh::expand_macro $::json_controller -debuginclusive $::debug__level ]
            
            set ip $::controller__instance__address
            set port $::controller__instance__port
            set vlan $::controller__instance__vlan


            tmsh::create /ltm virtual $name \
                 destination $ip:$port \
                 mirror enabled \
                 profiles replace-all-with \{ tcp http \} \
                 rules  \{ $name \} \
                 vlans replace-all-with \{ $vlan \} vlans-enabled 

        }
    }
}


gen_dag [ gen_net $::networking__vmac__base]
gen_contr

}
macro {
}
presentation {
section cluster {
    table active {
        choice nodes display "xlarge" tcl {
            tmsh::run_proc f5.app_utils:get_items cm device
        }
    }
    row standby {
        choice device display "xlarge" tcl {
            tmsh::run_proc f5.app_utils:get_items cm device
        }
    }
}

section networking {
    table net {
        choice vlan tcl {
            tmsh::run_proc f5.app_utils:get_items net vlan
        }
 string vlan_vid required display "small" 
 string address required
 choice dag display "large" {"Source Address"=> "nw_src", "Destination Address" => "nw_dst", "None/HA" => "ha" }
    }
    row vmac { string base required default "02:01:00:f5:00:01" }
}

section controller {
        row instance {
        choice type { "Directflow" => "json"}
        choice vlan tcl {
            tmsh::run_proc f5.app_utils:get_items net vlan
        }
        string address required
        string port required
        }
}

section disaggregation {
    table subscriber {
        string net
    }
    choice depth default "3" { "1","2","3","4","5","6","7","8","9","10"}
    choice optimiser default "yes" { "yes" => "1", "no" => "0" }
    choice idle default "30" { "30","120"}
    row update {
        choice type default "full" {"Incremental" => "incremental", "Full" => "full" }
    }
}

section debug {
        choice level default "0" { "0","1","2" }
}

text {
cluster "Device Service Cluster"
cluster.active "Active Nodes"
cluster.active.nodes ""
cluster.standby "Standby Node"
cluster.standby.device ""

networking "Cluster Networking"
networking.net "Dataplane"
networking.net.vlan "VLAN"
networking.net.vlan_vid "VLAN ID"
networking.net.address "Self IP Network"
networking.net.dag "Hash"
networking.vmac "Virtual MAC Address"
networking.vmac.base "Start Address"

controller "Cluster Controller"
controller.instance "Select Type"
controller.instance.type "Protocol"
controller.instance.vlan "VLAN"
controller.instance.address "Address"
controller.instance.port "Port"

disaggregation "Disaggregation"
disaggregation.subscriber "Subscriber"
disaggregation.subscriber.net "Networks"
disaggregation.depth "DAG depth"
disaggregation.optimiser "Optimise Ruleset"
disaggregation.idle "Idle Timeout"
disaggregation.update "Update"
disaggregation.update.type "Select Type" 

debug "Debugging"
debug.level "Log Level"

}
}
    role-acl none
    run-as none
}
}
description none
ignore-verification false
requires-bigip-version-max none
requires-bigip-version-min none
requires-modules { ltm }
signing-key none
tmpl-checksum none
tmpl-signature none
}

Tested this on version:

12.0
Published Jan 25, 2016
Version 1.0

Was this article helpful?

No CommentsBe the first to comment