Manage Netscaler in go

Fundamentals What is Netscaler? Netscalers are hardware based Loadbalancing Solutions, imagine you have multiple nodes serving same code copy and you want to balance traffic across all these nodes, one can use Netscalers to solve this usecase although it comes at a cost, this is a proprietary paid enterprise solution What are the configurations of Netscalers? Clients Connect to something called VIP(Virtual IP), Imagine this as a Virtual Machine running on Netscaler These Virtual IPs are configured as Virtual Servers(Vserver) in Netscalers Create ServiceGroup, this is a logical grouping of Services which are your backend Nodes, imagine that you have different capability servers, you might want to leverage this grouping capability to leverage different Loadbalancing Algorithms across multiple ServiceGroups Create Monitor, this can be TCP or HTTP based on what kind of backend Nodes you have and based on what health check you want to mark your backends up/down Create Services using ns commands and add your backend Nodes Bind Services to ServiceGroup Bind Vserver to ServiceGroup How to manage these configurations in go? Pre-requisites Installed go Import nitro golang module to manage Netscalers import ( "github.com/chiradeep/go-nitro/config/lb" "github.com/chiradeep/go-nitro/netscaler" ) Real Code, imagine you have more than a few Netscalers Assume we want to bind/unbind multiple Vservers and ServiceGroups Get Connections to all your netscalers allns := []string{"netcaler1","nestcaler2","netscaler3"} var nsClients map[string]*netscaler.NitroClient for _, cl := range c { client, err := netscaler.NewNitroClientFromParams(netscaler.NitroParams{Url: cl, Username: "", Password: "", SslVerify: false}) if err != nil { log.Fatal("Could not create a client: ", err1) } nsClients[cl] = client Create Jobs to bind/unbind servicegroups type Job struct { lbname string nsclients map[string]interface{} servicegroupname string nsIP string } var jobs = make(chan Job, 1) func allocate(allnsclients map[string]interface{}, file_name string) { dat, _ := os.Open(file_name) scanner := bufio.NewScanner(dat) re := regexp.MustCompile(`VSERVER:(.*?)::NS_IP:(.*?)::SVC_GRP:(.*?)`) flag.Parse() for scanner.Scan() { scannedText := scanner.Text() if re1.MatchString(scannedText) { match := re.FindStringSubmatch(text1) job := Job{lbname: match[1], nsclients: allnsclients, servicegroupname: match[3], nsIP: match[2]} jobs

Mar 14, 2025 - 05:05
 0
Manage Netscaler in go

Fundamentals

What is Netscaler?

Netscalers are hardware based Loadbalancing Solutions, imagine you have multiple nodes serving same code copy and you want to balance traffic across all these nodes, one can use Netscalers to solve this usecase although it comes at a cost, this is a proprietary paid enterprise solution

What are the configurations of Netscalers?

  1. Clients Connect to something called VIP(Virtual IP), Imagine this as a Virtual Machine running on Netscaler
  2. These Virtual IPs are configured as Virtual Servers(Vserver) in Netscalers
  3. Create ServiceGroup, this is a logical grouping of Services which are your backend Nodes, imagine that you have different capability servers, you might want to leverage this grouping capability to leverage different Loadbalancing Algorithms across multiple ServiceGroups
  4. Create Monitor, this can be TCP or HTTP based on what kind of backend Nodes you have and based on what health check you want to mark your backends up/down
  5. Create Services using ns commands and add your backend Nodes
  6. Bind Services to ServiceGroup
  7. Bind Vserver to ServiceGroup

How to manage these configurations in go?

Pre-requisites

  • Installed go
  • Import nitro golang module to manage Netscalers
import (
        "github.com/chiradeep/go-nitro/config/lb"
        "github.com/chiradeep/go-nitro/netscaler"
)

Real Code, imagine you have more than a few Netscalers

Assume we want to bind/unbind multiple Vservers and ServiceGroups

Get Connections to all your netscalers

allns := []string{"netcaler1","nestcaler2","netscaler3"}
    var nsClients map[string]*netscaler.NitroClient
    for _, cl := range c {
        client, err := netscaler.NewNitroClientFromParams(netscaler.NitroParams{Url: cl,
            Username:  "",
            Password:  "",
            SslVerify: false})
        if err != nil {
            log.Fatal("Could not create a client: ", err1)

        }
nsClients[cl] = client

Create Jobs to bind/unbind servicegroups


type Job struct {
    lbname           string
    nsclients        map[string]interface{}
    servicegroupname string
    nsIP             string
}
var jobs = make(chan Job, 1)

func allocate(allnsclients map[string]interface{}, file_name string) {
    dat, _ := os.Open(file_name)
    scanner := bufio.NewScanner(dat)
    re := regexp.MustCompile(`VSERVER:(.*?)::NS_IP:(.*?)::SVC_GRP:(.*?)`)
    flag.Parse()
    for scanner.Scan() {
        scannedText := scanner.Text()
        if re1.MatchString(scannedText) {
            match := re.FindStringSubmatch(text1)
            job := Job{lbname: match[1], nsclients: allnsclients, servicegroupname: match[3], nsIP: match[2]}
            jobs <- job
        }
    }
    close(jobs) // Make sure to do this
}

Consume the Job and Execute Action

  • Assuming action name is bind/unbind
type MyJobResult struct {
    Lb       string 
    JobState string
}

func worker(wg *sync.WaitGroup) {
    for job := range jobs {
        client := job.nsclients["https://"+job.nsIP].(*netscaler.NitroClient)

        myJr := MyJobResult{SLb: job.lbname}

        jobstate := "SUCCESS"
        jobsFailed := ""
        sgs := strings.Split(job.servicegroupname, ":")

        for _, sg := range sgs {
            binding := lb.Lbvserverservicegroupbinding{
                Name:             job.lbname,
                Servicegroupname: sg,
            }

            if *action == "bind" {
                result := client.BindResource(netscaler.Lbvserver.Type(), job.lbname, netscaler.Servicegroup.Type(), sg, &binding)
                if result != nil && !strings.Contains(result.Error(), "\"errorcode\": 273") {
                    jobstate = "FAILED"
                    jobsFailed += ":" + sg
                }
            } else if *action == "unbind" {
                result := client.UnbindResource(netscaler.Lbvserver.Type(), job.lbname, netscaler.Servicegroup.Type(), sg, "servicename")
                if result != nil && !strings.Contains(result.Error(), "\"errorcode\": 2641") {
                    jobstate = "FAILED"
                    jobsFailed += ":" + sg
                }
            }

        }
        myt.JobState = jobstate + jobsFailed
        results <- myJr
    }
    wg.Done()
}
func createWorkerPool(noOfWorkers int) {
    var wg sync.WaitGroup
    for i := 0; i < noOfWorkers; i++ {
        wg.Add(1)
        go worker(&wg)
    }
    wg.Wait()
    close(results) // Make sure we close results channel here
}

Consolidate the results

func print_result() {
    red := color.New(color.FgHiRed)
    green := color.New(color.FgHiGreen)
    blue := color.New(color.FgHiBlue)
    for taep, lb_state := range toptaep.Taepclouds {
        blue.Println(taep)
        for lb, state := range lb_state {
            if state == "SUCCESS" {
                green.Printf("\t%s::LBNAME:%s\n", state, lb)
            } else {
                red.Printf("\t%s::LBNAME:%s\n", state, lb)
            }
        }
    }
}

func result(done chan bool) {
    for result := range results {

        if consolidatedResult == nil {
            consolidatedResult = make(map[string]string)
        }
        consolidatedResult[result.Lb] = result.JobState
    }
    done <- true
}
}

Putting it all together

func main() {

    input_file := flag.String("file", "", "Raw File for Processing")
    action = flag.String("ac", "", "bind/unbind")
    flag.Parse()
    if *input_file == "" {
        flag.Usage()
        os.Exit(1)
    }
    _, err := os.Stat(*input_file)
    if os.IsNotExist(err) {
        fmt.Println("File does not exist")
        os.Exit(1)
    }
    allnsclients := getAllNSClients()
    go allocate(allnsclients, *input_file)
    done := make(chan bool)
    go result(done)
    noOfWorkers := 10
    createWorkerPool(noOfWorkers)
    <-done
    print_result()
}