From 4cf5e5c04b9f9b10f9b370faf0f277ac210c4b62 Mon Sep 17 00:00:00 2001 From: Vladimir Lavor Date: Thu, 1 Oct 2020 16:03:28 +0200 Subject: Updated multi-vpp example * In addition to configuring multiple VPPs it now also shows how to connect and read stats from several VPPs at once * Added a brief readme * More readable example log output * May run in a loop Change-Id: Ifa5d54e954557e7b6569826a48c526185ec751a3 Signed-off-by: Vladimir Lavor --- examples/multi-vpp/multi_vpp.go | 311 ++++++++++++++++++++++++++++------------ 1 file changed, 221 insertions(+), 90 deletions(-) (limited to 'examples/multi-vpp/multi_vpp.go') diff --git a/examples/multi-vpp/multi_vpp.go b/examples/multi-vpp/multi_vpp.go index 8714c9a..c42f802 100644 --- a/examples/multi-vpp/multi_vpp.go +++ b/examples/multi-vpp/multi_vpp.go @@ -20,9 +20,11 @@ import ( "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" @@ -33,89 +35,157 @@ import ( ) 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() + + 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) +} - // connect VPP2 - conn2, err := connectToVPP(*sockAddrVpp2, 2) +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") + logInfo("OK\n") - // retrieve configuration from the VPPs - retrieveIPAddresses(ch1, ifIdx1) - retrieveIPAddresses(ch2, ifIdx2) - - 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 } @@ -123,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{} @@ -133,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{} @@ -158,49 +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) + 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) +} -- cgit 1.2.3-korg