Updated multi-vpp example
[govpp.git] / examples / multi-vpp / multi_vpp.go
index 244dd03..c42f802 100644 (file)
@@ -18,103 +18,174 @@ package main
 import (
        "flag"
        "fmt"
+       "log"
+       "os"
+       "strings"
+
        "git.fd.io/govpp.git"
        "git.fd.io/govpp.git/adapter/socketclient"
+       "git.fd.io/govpp.git/adapter/statsclient"
        "git.fd.io/govpp.git/api"
+       interfaces "git.fd.io/govpp.git/binapi/interface"
+       "git.fd.io/govpp.git/binapi/interface_types"
+       "git.fd.io/govpp.git/binapi/ip"
+       "git.fd.io/govpp.git/binapi/ip_types"
+       "git.fd.io/govpp.git/binapi/vpe"
        "git.fd.io/govpp.git/core"
-       "git.fd.io/govpp.git/examples/binapi/interface_types"
-       "git.fd.io/govpp.git/examples/binapi/interfaces"
-       "git.fd.io/govpp.git/examples/binapi/ip"
-       "git.fd.io/govpp.git/examples/binapi/ip_types"
-       "git.fd.io/govpp.git/examples/binapi/vpe"
-       "log"
-       "os"
 )
 
 var (
-       sockAddrVpp1 = flag.String("sock1", socketclient.DefaultSocketName, "Path to binary API socket file of the first VPP instance")
-       sockAddrVpp2 = flag.String("sock2", socketclient.DefaultSocketName, "Path to binary API socket file of the second VPP instance")
+       binapiSockAddrVpp1 = flag.String("api-sock-1", socketclient.DefaultSocketName, "Path to binary API socket file of the VPP1")
+       statsSockAddrVpp1  = flag.String("stats-sock-1", statsclient.DefaultSocketName, "Path to stats socket file of the VPP1")
+       binapiSockAddrVpp2 = flag.String("api-sock-2", socketclient.DefaultSocketName, "Path to binary API socket file of the VPP2")
+       statsSockAddrVpp2  = flag.String("stats-sock-2", statsclient.DefaultSocketName, "Path to stats socket file of the VPP2")
 )
 
+var Errors []error
+
 func main() {
        flag.Parse()
        fmt.Println("Starting multi-vpp example")
 
-       // since both of them default to the same value
-       if *sockAddrVpp1 == *sockAddrVpp2 {
-               log.Fatalln("ERROR: identical VPP sockets defined, set at least one of them to non-default path")
-       }
+       defer func() {
+               if len(Errors) > 0 {
+                       logInfo("Finished with %d errors\n", len(Errors))
+                       os.Exit(1)
+               } else {
+                       logInfo("Finished successfully\n")
+               }
+       }()
 
-       // connect VPP1
-       conn1, err := connectToVPP(*sockAddrVpp1, 1)
-       if err != nil {
-               log.Fatalf("ERROR: connecting VPP failed (socket %s): %v\n", *sockAddrVpp1, err)
+       // since sockets default to the same value
+       if *binapiSockAddrVpp1 == *binapiSockAddrVpp2 {
+               log.Fatalln("ERROR: identical VPP binapi sockets defined, set at least one of them to a non-default path")
        }
-       defer conn1.Disconnect()
-       ch1, err := getAPIChannel(conn1)
-       if err != nil {
-               log.Fatalf("ERROR: creating channel failed (socket: %s): %v\n", *sockAddrVpp1, err)
+       if *statsSockAddrVpp1 == *statsSockAddrVpp2 {
+               log.Fatalln("ERROR: identical VPP stats sockets defined, set at least one of them to a non-default path")
        }
-       defer ch1.Close()
+       var name1, name2 = "vpp1", "vpp2"
+       ch1, statsConn1, disconnect1 := connectVPP(name1, *binapiSockAddrVpp1, *statsSockAddrVpp1)
+       defer disconnect1()
 
-       // connect VPP2
-       conn2, err := connectToVPP(*sockAddrVpp2, 2)
+       ch2, statsConn2, disconnect2 := connectVPP(name2, *binapiSockAddrVpp2, *statsSockAddrVpp2)
+       defer disconnect2()
+
+       fmt.Println()
+
+       // retrieve VPP1 version
+       logHeader("Retrieving %s version", name1)
+       getVppVersion(ch1, name1)
+
+       // retrieve VPP2 version
+       logHeader("Retrieving %s version", name2)
+       getVppVersion(ch1, name2)
+
+       // configure VPP1
+       logHeader("Configuring %s", name1)
+       ifIdx1 := createLoopback(ch1, name1)
+       addIPsToInterface(ch1, ifIdx1, []string{"10.10.0.1/24", "15.10.0.1/24"})
+
+       // configure VPP2
+       logHeader("Configuring %s", name2)
+       ifIdx2 := createLoopback(ch2, name2)
+       addIPsToInterface(ch2, ifIdx2, []string{"20.10.0.1/24", "25.10.0.1/24"})
+
+       // retrieve configuration from VPPs
+       retrieveIPAddresses(ch1, name1, ifIdx1)
+       retrieveIPAddresses(ch2, name2, ifIdx2)
+
+       // retrieve stats from VPPs
+       retrieveStats(statsConn1, name1)
+       retrieveStats(statsConn2, name2)
+
+       // cleanup
+       logHeader("Cleaning up %s", name1)
+       deleteIPsToInterface(ch1, ifIdx1, []string{"10.10.0.1/24", "15.10.0.1/24"})
+       deleteLoopback(ch1, ifIdx1)
+       logHeader("Cleaning up %s", name2)
+       deleteIPsToInterface(ch2, ifIdx2, []string{"20.10.0.1/24", "25.10.0.1/24"})
+       deleteLoopback(ch2, ifIdx2)
+}
+
+func connectVPP(name, binapiSocket, statsSocket string) (api.Channel, api.StatsProvider, func()) {
+       fmt.Println()
+       logHeader("Connecting to %s", name)
+
+       // connect VPP1 to the binapi socket
+       ch, disconnectBinapi, err := connectBinapi(binapiSocket, 1)
        if err != nil {
-               log.Fatalf("ERROR: connecting VPP failed (socket %s): %v\n", *sockAddrVpp2, err)
+               log.Fatalf("ERROR: connecting VPP binapi failed (socket %s): %v\n", binapiSocket, err)
        }
-       defer conn2.Disconnect()
-       ch2, err := getAPIChannel(conn2)
+
+       // connect VPP1 to the stats socket
+       statsConn, disconnectStats, err := connectStats(name, statsSocket)
        if err != nil {
-               log.Fatalf("ERROR: creating channel failed (socket: %s): %v\n", *sockAddrVpp2, err)
+               disconnectBinapi()
+               log.Fatalf("ERROR: connecting VPP stats failed (socket %s): %v\n", statsSocket, err)
        }
-       defer ch2.Close()
-
-       // configure VPPs
-       ifIdx1 := createLoopback(ch1)
-       addIPToInterface(ch1, ifIdx1, "10.10.0.1/24")
-       ifIdx2 := createLoopback(ch2)
-       addIPToInterface(ch2, ifIdx2, "20.10.0.1/24")
 
-       // retrieve configuration from the VPPs
-       retrieveIPAddresses(ch1, ifIdx1)
-       retrieveIPAddresses(ch2, ifIdx2)
+       logInfo("OK\n")
 
-       if len(Errors) > 0 {
-               fmt.Printf("finished with %d errors\n", len(Errors))
-               os.Exit(1)
-       } else {
-               fmt.Println("finished successfully")
+       return ch, statsConn, func() {
+               disconnectStats()
+               disconnectBinapi()
+               logInfo("VPP %s disconnected\n", name)
        }
 }
 
-func connectToVPP(socket string, attempts int) (*core.Connection, error) {
-       connection, event, err := govpp.AsyncConnect(socket, attempts, core.DefaultReconnectInterval)
+// connectBinapi connects to the binary API socket and returns a communication channel
+func connectBinapi(socket string, attempts int) (api.Channel, func(), error) {
+       logInfo("Attaching to the binapi socket %s\n", socket)
+       conn, event, err := govpp.AsyncConnect(socket, attempts, core.DefaultReconnectInterval)
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
-
-       // handle connection event
        select {
        case e := <-event:
                if e.State != core.Connected {
-                       return nil, err
+                       return nil, nil, err
                }
        }
-       return connection, nil
+       ch, err := getAPIChannel(conn)
+       if err != nil {
+               return nil, nil, err
+       }
+       disconnect := func() {
+               if ch != nil {
+                       ch.Close()
+               }
+               if conn != nil {
+                       conn.Disconnect()
+               }
+       }
+       return ch, disconnect, nil
 }
 
-func getAPIChannel(conn *core.Connection) (api.Channel, error) {
-       ch, err := conn.NewAPIChannel()
+// connectStats connects to the stats socket and returns a stats provider
+func connectStats(name, socket string) (api.StatsProvider, func(), error) {
+       logInfo("Attaching to the stats socket %s\n", socket)
+       sc := statsclient.NewStatsClient(socket)
+       conn, err := core.ConnectStats(sc)
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
+       disconnect := func() {
+               if err := sc.Disconnect(); err != nil {
+                       logError(err, "failed to disconnect "+name+" stats socket")
+               }
+       }
+       return conn, disconnect, nil
+}
 
+// getAPIChannel creates new API channel and verifies its compatibility
+func getAPIChannel(c api.ChannelProvider) (api.Channel, error) {
+       ch, err := c.NewAPIChannel()
+       if err != nil {
+               return nil, err
+       }
        if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
                return nil, err
        }
-
-       getVppVersion(ch)
-
        if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
                return nil, err
        }
@@ -122,8 +193,8 @@ func getAPIChannel(conn *core.Connection) (api.Channel, error) {
 }
 
 // getVppVersion returns VPP version (simple API usage)
-func getVppVersion(ch api.Channel) {
-       fmt.Println("Retrieving version")
+func getVppVersion(ch api.Channel, name string) {
+       logInfo("Retrieving version of %s ..\n", name)
 
        req := &vpe.ShowVersion{}
        reply := &vpe.ShowVersionReply{}
@@ -132,23 +203,13 @@ func getVppVersion(ch api.Channel) {
                logError(err, "retrieving version")
                return
        }
-       fmt.Printf("reply: %+v\n", reply)
-
-       fmt.Printf("VPP version: %q\n", reply.Version)
-       fmt.Println("OK")
+       logInfo("Retrieved version is %q\n", reply.Version)
        fmt.Println()
 }
 
-var Errors []error
-
-func logError(err error, msg string) {
-       fmt.Printf("ERROR: %s: %v\n", msg, err)
-       Errors = append(Errors, err)
-}
-
 // createLoopback sends request to create a loopback interface
-func createLoopback(ch api.Channel) interface_types.InterfaceIndex {
-       fmt.Println("Adding loopback interface")
+func createLoopback(ch api.Channel, name string) interface_types.InterfaceIndex {
+       logInfo("Adding loopback interface ..\n")
 
        req := &interfaces.CreateLoopback{}
        reply := &interfaces.CreateLoopbackReply{}
@@ -157,50 +218,85 @@ func createLoopback(ch api.Channel) interface_types.InterfaceIndex {
                logError(err, "adding loopback interface")
                return 0
        }
-       fmt.Printf("reply: %+v\n", reply)
-
-       fmt.Printf("interface index: %v\n", reply.SwIfIndex)
-       fmt.Println("OK")
-       fmt.Println()
+       logInfo("Interface index %d added to %s\n", reply.SwIfIndex, name)
 
        return reply.SwIfIndex
 }
 
-// addIPToInterface sends request to add an IP address to an interface.
-func addIPToInterface(ch api.Channel, index interface_types.InterfaceIndex, ip string) {
-       fmt.Printf("Setting up IP address to the interface with index %d\n", index)
-       prefix, err := ip_types.ParsePrefix(ip)
-       if err != nil {
-               logError(err, "attempt to add invalid IP address")
-               return
+// deleteLoopback removes created loopback interface
+func deleteLoopback(ch api.Channel, ifIdx interface_types.InterfaceIndex) {
+       logInfo("Removing loopback interface ..\n")
+       req := &interfaces.DeleteLoopback{
+               SwIfIndex: ifIdx,
        }
+       reply := &interfaces.DeleteLoopbackReply{}
 
-
-       req := &interfaces.SwInterfaceAddDelAddress{
-               SwIfIndex: index,
-               IsAdd:     true,
-               Prefix:    ip_types.AddressWithPrefix(prefix),
+       if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
+               logError(err, "removing loopback interface")
        }
-       reply := &interfaces.SwInterfaceAddDelAddressReply{}
+       logInfo("OK\n")
+       fmt.Println()
+}
 
-       if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
-               logError(err, "adding IP address to interface")
-               return
+// addIPsToInterface sends request to add IP addresses to an interface.
+func addIPsToInterface(ch api.Channel, index interface_types.InterfaceIndex, ips []string) {
+       for _, ipAddr := range ips {
+               logInfo("Adding IP address %s\n", ipAddr)
+               prefix, err := ip_types.ParsePrefix(ipAddr)
+               if err != nil {
+                       logError(err, "attempt to add invalid IP address")
+                       return
+               }
+
+               req := &interfaces.SwInterfaceAddDelAddress{
+                       SwIfIndex: index,
+                       IsAdd:     true,
+                       Prefix:    ip_types.AddressWithPrefix(prefix),
+               }
+               reply := &interfaces.SwInterfaceAddDelAddressReply{}
+
+               if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
+                       logError(err, "adding IP address to interface")
+                       return
+               }
        }
-       fmt.Printf("reply: %+v\n", reply)
 
-       fmt.Println("OK")
+       logInfo("OK\n")
        fmt.Println()
 }
 
-func retrieveIPAddresses(ch api.Channel, index interface_types.InterfaceIndex) {
-       fmt.Printf("Retrieving IP addresses for interface index %d\n", index)
+// deleteIPsToInterface sends request to remove IP addresses from an interface.
+func deleteIPsToInterface(ch api.Channel, index interface_types.InterfaceIndex, ips []string) {
+       for _, ipAddr := range ips {
+               logInfo("Removing IP address %s\n", ipAddr)
+               prefix, err := ip_types.ParsePrefix(ipAddr)
+               if err != nil {
+                       logError(err, "attempt to remove invalid IP address")
+                       return
+               }
+
+               req := &interfaces.SwInterfaceAddDelAddress{
+                       SwIfIndex: index,
+                       Prefix:    ip_types.AddressWithPrefix(prefix),
+               }
+               reply := &interfaces.SwInterfaceAddDelAddressReply{}
+
+               if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
+                       logError(err, "removing IP address to interface")
+                       return
+               }
+       }
+}
 
+// retrieveIPAddresses reads IP address from the interface
+func retrieveIPAddresses(ch api.Channel, name string, index interface_types.InterfaceIndex) {
+       logHeader("Retrieving interface data from %s", name)
        req := &ip.IPAddressDump{
                SwIfIndex: index,
        }
        reqCtx := ch.SendMultiRequest(req)
 
+       logInfo("Dump IP addresses for interface index %d ..\n", index)
        for {
                msg := &ip.IPAddressDetails{}
                stop, err := reqCtx.ReceiveReply(msg)
@@ -212,9 +308,44 @@ func retrieveIPAddresses(ch api.Channel, index interface_types.InterfaceIndex) {
                        break
                }
                prefix := ip_types.Prefix(msg.Prefix)
-               fmt.Printf(" - ip address: %+v\n", prefix.ToString())
+               logInfo(" - ip address: %v\n", prefix)
+       }
+
+       logInfo("OK\n")
+       fmt.Println()
+}
+
+// retrieveStats reads interface stats
+func retrieveStats(s api.StatsProvider, name string) {
+       logHeader("Retrieving interface stats from %s", name)
+       ifStats := &api.InterfaceStats{}
+       err := s.GetInterfaceStats(ifStats)
+       if err != nil {
+               logError(err, "dumping interface stats")
+               return
+       }
+       logInfo("Dump interface stats ..\n")
+       for _, ifStats := range ifStats.Interfaces {
+               logInfo(" - %+v\n", ifStats)
        }
 
-       fmt.Println("OK")
+       logInfo("OK\n")
        fmt.Println()
 }
+
+// logHeader prints underlined message (for better output segmentation)
+func logHeader(format string, a ...interface{}) {
+       n, _ := fmt.Printf(format+"\n", a...)
+       fmt.Println(strings.Repeat("-", n-1))
+}
+
+// logInfo prints info message
+func logInfo(format string, a ...interface{}) {
+       fmt.Printf(format, a...)
+}
+
+// logError prints error message
+func logError(err error, msg string) {
+       fmt.Printf("[ERROR]: %s: %v\n", msg, err)
+       Errors = append(Errors, err)
+}