From 57de49f7583b8174c7f3d8e21956d4eaac64ac28 Mon Sep 17 00:00:00 2001 From: Vladimir Lavor Date: Tue, 6 Jul 2021 14:17:36 +0200 Subject: feat: api-trace Signed-off-by: Vladimir Lavor Change-Id: I7de363dfb3930db13a30e97f154c57d75c07f01c --- examples/api-trace/README.md | 17 ++ examples/api-trace/api-trace.go | 348 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 examples/api-trace/README.md create mode 100644 examples/api-trace/api-trace.go (limited to 'examples') diff --git a/examples/api-trace/README.md b/examples/api-trace/README.md new file mode 100644 index 0000000..6cb6ce8 --- /dev/null +++ b/examples/api-trace/README.md @@ -0,0 +1,17 @@ +# API trace example + +The example demonstrates how to use GoVPP API trace functionality. Connection object `core.Connection` contains +API tracer able to record API messages sent to and from VPP. + +Access to the tracer is done via `Trace()`. It allows accessing several methods to manage collected entries: +* `Enable()` either enables or disables the trace. Note that the trace is disabled by default and messages are not recorded while so. +* `GetRecords() []*api.Record` provide messages collected since the plugin was enabled or cleared. +* `GetRecordsForChannel() []*api.Record` provide messages collected on the given channel since the plugin was enabled or cleared. +* `Clear()` removes recorded messages. + +A record is represented by `Record` type. It contains information about the message, its direction, time and channel ID. Following fields are available: +* `Message api.Message` returns recorded entry as GoVPP Message. +* `Timestamp time.Time` is the message timestamp. +* `IsReceived bool` is true if the message is a reply or details message, false otherwise. +* `ChannelID uint16` is the ID of channel processing the traced message. + diff --git a/examples/api-trace/api-trace.go b/examples/api-trace/api-trace.go new file mode 100644 index 0000000..3a78c7b --- /dev/null +++ b/examples/api-trace/api-trace.go @@ -0,0 +1,348 @@ +// Copyright (c) 2021 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// api-trace is and example how to use the GoVPP API trace tool. + +package main + +import ( + "context" + "flag" + "fmt" + "git.fd.io/govpp.git" + "git.fd.io/govpp.git/adapter/socketclient" + "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_types" + "git.fd.io/govpp.git/binapi/vpe" + "git.fd.io/govpp.git/core" + "log" +) + +var ( + sockAddr = flag.String("socket", socketclient.DefaultSocketName, "Path to VPP API socket file") +) + +func main() { + flag.Parse() + + fmt.Printf("Starting api-trace tool example\n\n") + + // make synchronous VPP connection + conn, err := govpp.Connect(*sockAddr) + if err != nil { + log.Fatalln("ERROR:", err) + } + defer conn.Disconnect() + + singleChannel(conn) + multiChannel(conn) + stream(conn) + + fmt.Printf("Api-trace tool example finished\n\n") +} + +func singleChannel(conn *core.Connection) { + // create new channel and perform simple compatibility check + ch, err := conn.NewAPIChannel() + if err != nil { + log.Fatalln("ERROR: creating channel failed:", err) + } + defer ch.Close() + + fmt.Printf("=> Example 1\n\nEnabling API trace...\n") + conn.Trace().Enable(true) + + if err := ch.CheckCompatiblity(append(vpe.AllMessages(), interfaces.AllMessages()...)...); err != nil { + log.Fatal(err) + } + + // do some API calls + fmt.Printf("Calling VPP API...\n") + retrieveVersion(ch) + idx := createLoopback(ch) + addIPAddress("10.10.0.1/24", ch, idx) + interfaceDump(ch) + deleteLoopback(ch, idx) + fmt.Println() + + fmt.Printf("API trace (api calls: %d):\n", len(conn.Trace().GetRecords())) + fmt.Printf("--------------------\n") + for _, item := range conn.Trace().GetRecords() { + printTrace(item) + } + fmt.Printf("--------------------\n") + + fmt.Printf("Clearing API trace...\n\n") + conn.Trace().Clear() +} + +func multiChannel(conn *core.Connection) { + ch1, err := conn.NewAPIChannel() + if err != nil { + log.Fatalln("ERROR: creating channel failed:", err) + } + defer ch1.Close() + ch2, err := conn.NewAPIChannel() + if err != nil { + log.Fatalln("ERROR: creating channel failed:", err) + } + defer ch2.Close() + + //do API calls again + fmt.Printf("=> Example 2\n\nCalling VPP API (multi-channel)...\n") + retrieveVersion(ch1) + idx1 := createLoopback(ch1) + idx2 := createLoopback(ch2) + addIPAddress("20.10.0.1/24", ch1, idx1) + addIPAddress("30.10.0.1/24", ch2, idx2) + interfaceDump(ch1) + deleteLoopback(ch2, idx1) + deleteLoopback(ch1, idx2) + fmt.Println() + + chan1, ok := ch1.(*core.Channel) + if !ok { + log.Fatalln("ERROR: incorrect type of channel 1:", err) + } + chan2, ok := ch2.(*core.Channel) + if !ok { + log.Fatalln("ERROR: incorrect type of channel 2:", err) + } + + fmt.Printf("API trace for channel 1 (api calls: %d):\n", len(conn.Trace().GetRecordsForChannel(chan1.GetID()))) + fmt.Printf("--------------------\n") + for _, item := range conn.Trace().GetRecordsForChannel(chan1.GetID()) { + printTrace(item) + } + fmt.Printf("--------------------\n") + fmt.Printf("API trace for channel 2 (api calls: %d):\n", len(conn.Trace().GetRecordsForChannel(chan2.GetID()))) + fmt.Printf("--------------------\n") + for _, item := range conn.Trace().GetRecordsForChannel(chan2.GetID()) { + printTrace(item) + } + fmt.Printf("--------------------\n") + fmt.Printf("cumulative API trace (api calls: %d):\n", len(conn.Trace().GetRecords())) + fmt.Printf("--------------------\n") + for _, item := range conn.Trace().GetRecords() { + printTrace(item) + } + fmt.Printf("--------------------\n") + + fmt.Printf("Clearing API trace...\n\n") + conn.Trace().Clear() +} + +func stream(conn *core.Connection) { + // create new channel and perform simple compatibility check + s, err := conn.NewStream(context.Background()) + if err != nil { + log.Fatalln("ERROR: creating channel failed:", err) + } + defer func() { + if err := s.Close(); err != nil { + log.Fatalf("failed to close stream: %v", err) + } + }() + + // do some API calls + fmt.Printf("=> Example 3\n\nCalling VPP API (stream)...\n") + invokeRetrieveVersion(conn) + idx := invokeCreateLoopback(conn) + invokeAddIPAddress("40.10.0.1/24", conn, idx) + invokeInterfaceDump(conn) + invokeDeleteLoopback(conn, idx) + fmt.Println() + + fmt.Printf("stream API trace (api calls: %d):\n", len(conn.Trace().GetRecords())) + fmt.Printf("--------------------\n") + for _, item := range conn.Trace().GetRecords() { + printTrace(item) + } + fmt.Printf("--------------------\n") + + fmt.Printf("Clearing API trace...\n\n") + conn.Trace().GetRecords() +} + +func retrieveVersion(ch api.Channel) { + req := &vpe.ShowVersion{} + reply := &vpe.ShowVersionReply{} + + if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - retrieved VPP version: %s\n", reply.Version) +} + +func invokeRetrieveVersion(c api.Connection) { + req := &vpe.ShowVersion{} + reply := &vpe.ShowVersionReply{} + + if err := c.Invoke(context.Background(), req, reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + } + fmt.Printf(" - retrieved VPP version: %s\n", reply.Version) +} + +func createLoopback(ch api.Channel) interface_types.InterfaceIndex { + req := &interfaces.CreateLoopback{} + reply := &interfaces.CreateLoopbackReply{} + + if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return 0 + } + fmt.Printf(" - created loopback with index: %d\n", reply.SwIfIndex) + return reply.SwIfIndex +} + +func invokeCreateLoopback(c api.Connection) interface_types.InterfaceIndex { + req := &interfaces.CreateLoopback{} + reply := &interfaces.CreateLoopbackReply{} + + if err := c.Invoke(context.Background(), req, reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return 0 + } + fmt.Printf(" - created loopback with index: %d\n", reply.SwIfIndex) + return reply.SwIfIndex +} + +func deleteLoopback(ch api.Channel, index interface_types.InterfaceIndex) { + req := &interfaces.DeleteLoopback{ + SwIfIndex: index, + } + reply := &interfaces.DeleteLoopbackReply{} + + if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - deleted loopback with index: %d\n", index) +} + +func invokeDeleteLoopback(c api.Connection, index interface_types.InterfaceIndex) { + req := &interfaces.DeleteLoopback{ + SwIfIndex: index, + } + reply := &interfaces.DeleteLoopbackReply{} + + if err := c.Invoke(context.Background(), req, reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - deleted loopback with index: %d\n", index) +} + +func addIPAddress(addr string, ch api.Channel, index interface_types.InterfaceIndex) { + ipAddr, err := ip_types.ParsePrefix(addr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + req := &interfaces.SwInterfaceAddDelAddress{ + SwIfIndex: index, + IsAdd: true, + Prefix: ip_types.AddressWithPrefix(ipAddr), + } + reply := &interfaces.SwInterfaceAddDelAddressReply{} + + if err := ch.SendRequest(req).ReceiveReply(reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - IP address %s added to interface with index %d\n", addr, index) +} + +func invokeAddIPAddress(addr string, c api.Connection, index interface_types.InterfaceIndex) { + ipAddr, err := ip_types.ParsePrefix(addr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + req := &interfaces.SwInterfaceAddDelAddress{ + SwIfIndex: index, + IsAdd: true, + Prefix: ip_types.AddressWithPrefix(ipAddr), + } + reply := &interfaces.SwInterfaceAddDelAddressReply{} + + if err := c.Invoke(context.Background(), req, reply); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - IP address %s added to interface with index %d\n", addr, index) +} + +func interfaceDump(ch api.Channel) { + reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + for { + msg := &interfaces.SwInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(msg) + if stop { + break + } + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + fmt.Printf(" - retrieved interface: %v (idx: %d)\n", msg.InterfaceName, msg.SwIfIndex) + } +} + +func invokeInterfaceDump(c api.Connection) { + s, err := c.NewStream(context.Background()) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + if err := s.SendMsg(&interfaces.SwInterfaceDump{}); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + if err := s.SendMsg(&vpe.ControlPing{}); err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + for { + reply, err := s.RecvMsg() + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + switch msg := reply.(type) { + case *interfaces.SwInterfaceDetails: + fmt.Printf(" - retrieved interface: %v (idx: %d)\n", msg.InterfaceName, msg.SwIfIndex) + case *vpe.ControlPingReply: + return + } + } +} + +func printTrace(item *api.Record) { + h, m, s := item.Timestamp.Clock() + reply := "" + if item.IsReceived { + reply = "(reply)" + } + fmt.Printf("%dh:%dm:%ds:%dns %s %s\n", h, m, s, + item.Timestamp.Nanosecond(), item.Message.GetMessageName(), reply) +} -- cgit 1.2.3-korg