Change module name to go.fd.io/govpp
[govpp.git] / examples / multi-vpp / multi_vpp.go
1 // Copyright (c) 2020 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // multi-vpp is an example of managing multiple VPPs in single application.
16 package main
17
18 import (
19         "flag"
20         "fmt"
21         "log"
22         "os"
23         "strings"
24
25         "go.fd.io/govpp"
26         "go.fd.io/govpp/adapter/socketclient"
27         "go.fd.io/govpp/adapter/statsclient"
28         "go.fd.io/govpp/api"
29         interfaces "go.fd.io/govpp/binapi/interface"
30         "go.fd.io/govpp/binapi/interface_types"
31         "go.fd.io/govpp/binapi/ip"
32         "go.fd.io/govpp/binapi/ip_types"
33         "go.fd.io/govpp/binapi/vpe"
34         "go.fd.io/govpp/core"
35 )
36
37 var (
38         binapiSockAddrVpp1 = flag.String("api-sock-1", socketclient.DefaultSocketName, "Path to binary API socket file of the VPP1")
39         statsSockAddrVpp1  = flag.String("stats-sock-1", statsclient.DefaultSocketName, "Path to stats socket file of the VPP1")
40         binapiSockAddrVpp2 = flag.String("api-sock-2", socketclient.DefaultSocketName, "Path to binary API socket file of the VPP2")
41         statsSockAddrVpp2  = flag.String("stats-sock-2", statsclient.DefaultSocketName, "Path to stats socket file of the VPP2")
42 )
43
44 var errors []error
45
46 func main() {
47         flag.Parse()
48         fmt.Println("Starting multi-vpp example")
49
50         defer func() {
51                 if len(errors) > 0 {
52                         logInfo("Finished with %d errors\n", len(errors))
53                         os.Exit(1)
54                 } else {
55                         logInfo("Finished successfully\n")
56                 }
57         }()
58
59         // since sockets default to the same value
60         if *binapiSockAddrVpp1 == *binapiSockAddrVpp2 {
61                 log.Fatalln("ERROR: identical VPP binapi sockets defined, set at least one of them to a non-default path")
62         }
63         if *statsSockAddrVpp1 == *statsSockAddrVpp2 {
64                 log.Fatalln("ERROR: identical VPP stats sockets defined, set at least one of them to a non-default path")
65         }
66         var name1, name2 = "vpp1", "vpp2"
67         ch1, statsConn1, disconnect1 := connectVPP(name1, *binapiSockAddrVpp1, *statsSockAddrVpp1)
68         defer disconnect1()
69
70         ch2, statsConn2, disconnect2 := connectVPP(name2, *binapiSockAddrVpp2, *statsSockAddrVpp2)
71         defer disconnect2()
72
73         fmt.Println()
74
75         // retrieve VPP1 version
76         logHeader("Retrieving %s version", name1)
77         getVppVersion(ch1, name1)
78
79         // retrieve VPP2 version
80         logHeader("Retrieving %s version", name2)
81         getVppVersion(ch1, name2)
82
83         // configure VPP1
84         logHeader("Configuring %s", name1)
85         ifIdx1 := createLoopback(ch1, name1)
86         addIPsToInterface(ch1, ifIdx1, []string{"10.10.0.1/24", "15.10.0.1/24"})
87
88         // configure VPP2
89         logHeader("Configuring %s", name2)
90         ifIdx2 := createLoopback(ch2, name2)
91         addIPsToInterface(ch2, ifIdx2, []string{"20.10.0.1/24", "25.10.0.1/24"})
92
93         // retrieve configuration from VPPs
94         retrieveIPAddresses(ch1, name1, ifIdx1)
95         retrieveIPAddresses(ch2, name2, ifIdx2)
96
97         // retrieve stats from VPPs
98         retrieveStats(statsConn1, name1)
99         retrieveStats(statsConn2, name2)
100
101         // cleanup
102         logHeader("Cleaning up %s", name1)
103         deleteIPsToInterface(ch1, ifIdx1, []string{"10.10.0.1/24", "15.10.0.1/24"})
104         deleteLoopback(ch1, ifIdx1)
105         logHeader("Cleaning up %s", name2)
106         deleteIPsToInterface(ch2, ifIdx2, []string{"20.10.0.1/24", "25.10.0.1/24"})
107         deleteLoopback(ch2, ifIdx2)
108 }
109
110 func connectVPP(name, binapiSocket, statsSocket string) (api.Channel, api.StatsProvider, func()) {
111         fmt.Println()
112         logHeader("Connecting to %s", name)
113
114         // connect VPP1 to the binapi socket
115         ch, disconnectBinapi, err := connectBinapi(binapiSocket, 1)
116         if err != nil {
117                 log.Fatalf("ERROR: connecting VPP binapi failed (socket %s): %v\n", binapiSocket, err)
118         }
119
120         // connect VPP1 to the stats socket
121         statsConn, disconnectStats, err := connectStats(name, statsSocket)
122         if err != nil {
123                 disconnectBinapi()
124                 log.Fatalf("ERROR: connecting VPP stats failed (socket %s): %v\n", statsSocket, err)
125         }
126
127         logInfo("OK\n")
128
129         return ch, statsConn, func() {
130                 disconnectStats()
131                 disconnectBinapi()
132                 logInfo("VPP %s disconnected\n", name)
133         }
134 }
135
136 // connectBinapi connects to the binary API socket and returns a communication channel
137 func connectBinapi(socket string, attempts int) (api.Channel, func(), error) {
138         logInfo("Attaching to the binapi socket %s\n", socket)
139         conn, event, err := govpp.AsyncConnect(socket, attempts, core.DefaultReconnectInterval)
140         if err != nil {
141                 return nil, nil, err
142         }
143         select {
144         case e := <-event:
145                 if e.State != core.Connected {
146                         return nil, nil, err
147                 }
148         }
149         ch, err := getAPIChannel(conn)
150         if err != nil {
151                 return nil, nil, err
152         }
153         disconnect := func() {
154                 if ch != nil {
155                         ch.Close()
156                 }
157                 if conn != nil {
158                         conn.Disconnect()
159                 }
160         }
161         return ch, disconnect, nil
162 }
163
164 // connectStats connects to the stats socket and returns a stats provider
165 func connectStats(name, socket string) (api.StatsProvider, func(), error) {
166         logInfo("Attaching to the stats socket %s\n", socket)
167         sc := statsclient.NewStatsClient(socket)
168         conn, err := core.ConnectStats(sc)
169         if err != nil {
170                 return nil, nil, err
171         }
172         disconnect := func() {
173                 if err := sc.Disconnect(); err != nil {
174                         logError(err, "failed to disconnect "+name+" stats socket")
175                 }
176         }
177         return conn, disconnect, nil
178 }
179
180 // getAPIChannel creates new API channel and verifies its compatibility
181 func getAPIChannel(c api.ChannelProvider) (api.Channel, error) {
182         ch, err := c.NewAPIChannel()
183         if err != nil {
184                 return nil, err
185         }
186         if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
187                 return nil, err
188         }
189         if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
190                 return nil, err
191         }
192         return ch, nil
193 }
194
195 // getVppVersion returns VPP version (simple API usage)
196 func getVppVersion(ch api.Channel, name string) {
197         logInfo("Retrieving version of %s ..\n", name)
198
199         req := &vpe.ShowVersion{}
200         reply := &vpe.ShowVersionReply{}
201
202         if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
203                 logError(err, "retrieving version")
204                 return
205         }
206         logInfo("Retrieved version is %q\n", reply.Version)
207         fmt.Println()
208 }
209
210 // createLoopback sends request to create a loopback interface
211 func createLoopback(ch api.Channel, name string) interface_types.InterfaceIndex {
212         logInfo("Adding loopback interface ..\n")
213
214         req := &interfaces.CreateLoopback{}
215         reply := &interfaces.CreateLoopbackReply{}
216
217         if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
218                 logError(err, "adding loopback interface")
219                 return 0
220         }
221         logInfo("Interface index %d added to %s\n", reply.SwIfIndex, name)
222
223         return reply.SwIfIndex
224 }
225
226 // deleteLoopback removes created loopback interface
227 func deleteLoopback(ch api.Channel, ifIdx interface_types.InterfaceIndex) {
228         logInfo("Removing loopback interface ..\n")
229         req := &interfaces.DeleteLoopback{
230                 SwIfIndex: ifIdx,
231         }
232         reply := &interfaces.DeleteLoopbackReply{}
233
234         if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
235                 logError(err, "removing loopback interface")
236         }
237         logInfo("OK\n")
238         fmt.Println()
239 }
240
241 // addIPsToInterface sends request to add IP addresses to an interface.
242 func addIPsToInterface(ch api.Channel, index interface_types.InterfaceIndex, ips []string) {
243         for _, ipAddr := range ips {
244                 logInfo("Adding IP address %s\n", ipAddr)
245                 prefix, err := ip_types.ParsePrefix(ipAddr)
246                 if err != nil {
247                         logError(err, "attempt to add invalid IP address")
248                         return
249                 }
250
251                 req := &interfaces.SwInterfaceAddDelAddress{
252                         SwIfIndex: index,
253                         IsAdd:     true,
254                         Prefix:    ip_types.AddressWithPrefix(prefix),
255                 }
256                 reply := &interfaces.SwInterfaceAddDelAddressReply{}
257
258                 if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
259                         logError(err, "adding IP address to interface")
260                         return
261                 }
262         }
263
264         logInfo("OK\n")
265         fmt.Println()
266 }
267
268 // deleteIPsToInterface sends request to remove IP addresses from an interface.
269 func deleteIPsToInterface(ch api.Channel, index interface_types.InterfaceIndex, ips []string) {
270         for _, ipAddr := range ips {
271                 logInfo("Removing IP address %s\n", ipAddr)
272                 prefix, err := ip_types.ParsePrefix(ipAddr)
273                 if err != nil {
274                         logError(err, "attempt to remove invalid IP address")
275                         return
276                 }
277
278                 req := &interfaces.SwInterfaceAddDelAddress{
279                         SwIfIndex: index,
280                         Prefix:    ip_types.AddressWithPrefix(prefix),
281                 }
282                 reply := &interfaces.SwInterfaceAddDelAddressReply{}
283
284                 if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
285                         logError(err, "removing IP address to interface")
286                         return
287                 }
288         }
289 }
290
291 // retrieveIPAddresses reads IP address from the interface
292 func retrieveIPAddresses(ch api.Channel, name string, index interface_types.InterfaceIndex) {
293         logHeader("Retrieving interface data from %s", name)
294         req := &ip.IPAddressDump{
295                 SwIfIndex: index,
296         }
297         reqCtx := ch.SendMultiRequest(req)
298
299         logInfo("Dump IP addresses for interface index %d ..\n", index)
300         for {
301                 msg := &ip.IPAddressDetails{}
302                 stop, err := reqCtx.ReceiveReply(msg)
303                 if err != nil {
304                         logError(err, "dumping IP addresses")
305                         return
306                 }
307                 if stop {
308                         break
309                 }
310                 prefix := ip_types.Prefix(msg.Prefix)
311                 logInfo(" - ip address: %v\n", prefix)
312         }
313
314         logInfo("OK\n")
315         fmt.Println()
316 }
317
318 // retrieveStats reads interface stats
319 func retrieveStats(s api.StatsProvider, name string) {
320         logHeader("Retrieving interface stats from %s", name)
321         ifStats := &api.InterfaceStats{}
322         err := s.GetInterfaceStats(ifStats)
323         if err != nil {
324                 logError(err, "dumping interface stats")
325                 return
326         }
327         logInfo("Dump interface stats ..\n")
328         for _, ifStats := range ifStats.Interfaces {
329                 logInfo(" - %+v\n", ifStats)
330         }
331
332         logInfo("OK\n")
333         fmt.Println()
334 }
335
336 // logHeader prints underlined message (for better output segmentation)
337 func logHeader(format string, a ...interface{}) {
338         n, _ := fmt.Printf(format+"\n", a...)
339         fmt.Println(strings.Repeat("-", n-1))
340 }
341
342 // logInfo prints info message
343 func logInfo(format string, a ...interface{}) {
344         fmt.Printf(format, a...)
345 }
346
347 // logError prints error message
348 func logError(err error, msg string) {
349         fmt.Printf("[ERROR]: %s: %v\n", msg, err)
350         errors = append(errors, err)
351 }