aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVladimir Lavor <vlavor@cisco.com>2020-10-01 16:03:28 +0200
committerOndrej Fabry <ofabry@cisco.com>2020-10-10 04:43:50 +0000
commit4cf5e5c04b9f9b10f9b370faf0f277ac210c4b62 (patch)
treed9cfab38356c2177913125391e2c3271c27a2581
parentc2456559a66107441addb96f673191bde09d6977 (diff)
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 <vlavor@cisco.com>
-rw-r--r--examples/multi-vpp/README.md52
-rw-r--r--examples/multi-vpp/multi_vpp.go311
2 files changed, 273 insertions, 90 deletions
diff --git a/examples/multi-vpp/README.md b/examples/multi-vpp/README.md
new file mode 100644
index 0000000..2999ae9
--- /dev/null
+++ b/examples/multi-vpp/README.md
@@ -0,0 +1,52 @@
+# Multi-VPP example
+
+This example shows how to use GoVPP client to connect, configure and read stats from multiple VPPs simultaneously.
+
+# Requirements
+
+* VPP 19.08 or newer (required for stats client)
+* VPP stats enabled
+
+The example requires two running VPP instances. VPPs can be simply started in the same machine with different startup configs. Note that the default path to binary API and stats sockets are `/run/vpp/api.sock` or `/run/vpp/stats.sock` respectively. The example always uses the default path if none is set. It means that at least one VPP must have the following fields redefined:
+
+```
+socksvr {
+ socket-name /run/custom-vpp-path/api.sock
+}
+
+statseg {
+ socket-name /run/custom-vpp-path/stats.sock
+}
+```
+
+And the custom path must be provided to the example. Four flags are available:
+```
+-api-sock-1 string - Path to binary API socket file of the VPP1 (default "/run/vpp/api.sock")
+-api-sock-2 string - Path to binary API socket file of the VPP2 (default "/run/vpp/api.sock")
+-stats-sock-1 string - Path to stats socket file of the VPP1 (default "/run/vpp/stats.sock")
+-stats-sock-2 string - Path to stats socket file of the VPP2 (default "/run/vpp/stats.sock")
+```
+Let's say the VPP1 uses the default config, and the config above belongs to the VPP2. In that case, use the following command:
+```
+sudo ./multi-vpp -api-sock-2=/run/custom-vpp-path/api.sock -stats-sock-2=/run/custom-vpp-path/stats.sock
+```
+
+# Running the example
+
+The example consists of the following steps:
+* connects to both VPPs binary API socket and stats socket
+* configures example interfaces with IP addresses
+* dumps interface data via the binary API
+* dumps interface data via socket client
+* in case there are no errors, cleans up VPPs in order to be able running the example in a loop
+
+
+
+
+
+
+
+
+
+
+
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)
+}