go.ligato.io/vpp-agent/v3@v3.5.0/tests/integration/vpp/integration_test.go (about) 1 // Copyright (c) 2018 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 package vpp 16 17 import ( 18 "bytes" 19 "context" 20 "errors" 21 "fmt" 22 "io" 23 "os" 24 "os/exec" 25 "path" 26 "reflect" 27 "strings" 28 "syscall" 29 "testing" 30 "time" 31 32 "github.com/mitchellh/go-ps" 33 . "github.com/onsi/gomega" 34 "go.fd.io/govpp/adapter" 35 "go.fd.io/govpp/adapter/socketclient" 36 "go.fd.io/govpp/adapter/statsclient" 37 govppapi "go.fd.io/govpp/api" 38 govppcore "go.fd.io/govpp/core" 39 40 "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" 41 "go.ligato.io/vpp-agent/v3/plugins/vpp" 42 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi" 43 ) 44 45 const ( 46 vppConnectRetryDelay = time.Millisecond * 500 47 vppBootDelay = time.Millisecond * 200 48 vppTermDelay = time.Millisecond * 50 49 vppExitTimeout = time.Second * 1 50 51 defaultVPPConfig = ` 52 unix { 53 nodaemon 54 cli-listen /run/vpp/cli.sock 55 cli-no-pager 56 log /tmp/vpp.log 57 full-coredump 58 } 59 api-trace { 60 on 61 } 62 socksvr { 63 socket-name /run/vpp/api.sock 64 } 65 statseg { 66 socket-name /run/vpp/stats.sock 67 per-node-counters on 68 } 69 plugins { 70 plugin dpdk_plugin.so { disable } 71 }` 72 // in older versions of VPP (<=20.09), NAT plugin was also configured via the startup config file 73 withNatStartupConf = ` 74 nat { 75 endpoint-dependent 76 }` 77 ) 78 79 type TestCtx struct { 80 t *testing.T 81 Ctx context.Context 82 vppCmd *exec.Cmd 83 stderr, stdout *bytes.Buffer 84 Conn *govppcore.Connection 85 StatsConn *govppcore.StatsConnection 86 vppBinapi govppapi.Channel 87 vppStats govppapi.StatsProvider 88 vpp vppcalls.VppCoreAPI 89 versionInfo *vppcalls.VersionInfo 90 vppClient *vppClient 91 } 92 93 func startVPP(t *testing.T, stdout, stderr io.Writer) *exec.Cmd { 94 // check if VPP process is not running already 95 processes, err := ps.Processes() 96 if err != nil { 97 t.Fatalf("listing processes failed: %v", err) 98 } 99 for _, process := range processes { 100 proc := process.Executable() 101 if strings.Contains(proc, "vpp") && process.Pid() != os.Getpid() { 102 t.Logf(" - found process: %+v", process) 103 } 104 switch proc { 105 case *vppPath, "vpp", "vpp_main": 106 t.Fatalf("VPP is already running (PID: %v)", process.Pid()) 107 } 108 } 109 110 // remove binapi files from previous run 111 var removeFile = func(path string) { 112 if err := os.Remove(path); err == nil { 113 t.Logf("removed file %q", path) 114 } else if !os.IsNotExist(err) { 115 t.Fatalf("removing file %q failed: %v", path, err) 116 } 117 } 118 removeFile(*vppSockAddr) 119 120 // ensure VPP runtime directory exists 121 if err := os.Mkdir("/run/vpp", 0755); err != nil && !os.IsExist(err) { 122 t.Logf("mkdir failed: %v", err) 123 } 124 125 // setup VPP process 126 vppCmd := exec.Command(*vppPath) 127 if *vppConfig != "" { 128 vppCmd.Args = append(vppCmd.Args, "-c", *vppConfig) 129 } else { 130 config := defaultVPPConfig 131 if os.Getenv("VPPVER") <= "20.09" { 132 config += withNatStartupConf 133 } 134 vppCmd.Args = append(vppCmd.Args, config) 135 } 136 if *debug { 137 vppCmd.Stderr = os.Stderr 138 vppCmd.Stdout = os.Stdout 139 } else { 140 vppCmd.Stderr = stderr 141 vppCmd.Stdout = stdout 142 } 143 144 // ensure that process is killed when current process exits 145 vppCmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL} 146 147 if err := vppCmd.Start(); err != nil { 148 t.Fatalf("starting VPP failed: %v", err) 149 } 150 151 t.Logf("VPP start OK (PID: %v)", vppCmd.Process.Pid) 152 return vppCmd 153 } 154 155 // reRegisterMessage overwrites the original registration of Messages in GoVPP with new message registration. 156 func reRegisterMessage(x govppapi.Message) { 157 typ := reflect.TypeOf(x) 158 namecrc := x.GetMessageName() + "_" + x.GetCrcString() 159 binapiPath := path.Dir(reflect.TypeOf(x).Elem().PkgPath()) 160 govppapi.GetRegisteredMessages()[binapiPath][namecrc] = x 161 govppapi.GetRegisteredMessageTypes()[binapiPath][typ] = namecrc 162 } 163 164 func hackForBugInGoVPPMessageCache(t *testing.T, adapter adapter.VppAPI, vppCmd *exec.Cmd) error { 165 // connect to VPP 166 conn, apiChannel, _ := connectToBinAPI(t, adapter, vppCmd) 167 binapiVersion, err := binapi.CompatibleVersion(apiChannel) 168 if err != nil { 169 return err 170 } 171 172 // overwrite messages with messages from correct VPP version 173 for _, msg := range binapi.Versions[binapiVersion].AllMessages() { 174 reRegisterMessage(msg) 175 } 176 177 // disconnect from VPP (GoVPP is caching the messages that we want to override 178 // by first connection to VPP -> we must disconnect and reconnect later again) 179 disconnectBinAPI(t, conn, apiChannel, nil) 180 181 return nil 182 } 183 184 func setupVPP(t *testing.T) *TestCtx { 185 RegisterTestingT(t) 186 187 start := time.Now() 188 189 ctx := context.TODO() 190 191 // start VPP process 192 var stderr, stdout bytes.Buffer 193 vppCmd := startVPP(t, &stdout, &stderr) 194 vppPID := uint32(vppCmd.Process.Pid) 195 196 // if setupVPP fails we need stop the VPP process 197 defer func() { 198 if t.Failed() { 199 stopVPP(t, vppCmd) 200 } 201 }() 202 203 // wait until the socket is ready 204 adapter := socketclient.NewVppClient(*vppSockAddr) 205 if err := adapter.WaitReady(); err != nil { 206 t.Logf("WaitReady error: %v", err) 207 } 208 time.Sleep(vppBootDelay) 209 210 // FIXME: this is a hack for GoVPP bug when register of the same message(same CRC and name) but different 211 // VPP version overwrites the already registered message from one VPP version (map key is only CRC+name 212 // and that didn't change with VPP version, but generated binapi generated 2 different go types for it). 213 // Similar fix exists also for govppmux. 214 if err := hackForBugInGoVPPMessageCache(t, adapter, vppCmd); err != nil { 215 t.Fatal("can't apply hack fixing bug in GoVPP regarding stream's message type resolving") 216 } 217 218 // connect to VPP's binary API 219 conn, apiChannel, vppClient := connectToBinAPI(t, adapter, vppCmd) 220 vpeHandler := vppcalls.CompatibleHandler(vppClient) 221 222 // retrieve VPP version 223 versionInfo, err := vpeHandler.GetVersion(ctx) 224 if err != nil { 225 t.Fatalf("getting version info failed: %v", err) 226 } 227 t.Logf("VPP version: %v", versionInfo.Version) 228 if versionInfo.Version == "" { 229 t.Fatal("expected VPP version to not be empty") 230 } 231 // verify connected session 232 vpeInfo, err := vpeHandler.GetSession(ctx) 233 if err != nil { 234 t.Fatalf("getting vpp info failed: %v", err) 235 } 236 if vpeInfo.PID != vppPID { 237 t.Fatalf("expected VPP PID to be %v, got %v", vppPID, vpeInfo.PID) 238 } 239 240 vppClient.vpp = vpeHandler 241 242 // connect to stats 243 statsClient := statsclient.NewStatsClient("") 244 statsConn, err := govppcore.ConnectStats(statsClient) 245 if err != nil { 246 t.Logf("connecting to VPP stats API failed: %v", err) 247 } else { 248 vppClient.stats = statsConn 249 } 250 251 t.Logf("-> VPP ready (took %v)", time.Since(start).Seconds()) 252 253 return &TestCtx{ 254 t: t, 255 Ctx: ctx, 256 versionInfo: versionInfo, 257 vpp: vpeHandler, 258 vppCmd: vppCmd, 259 stderr: &stderr, 260 stdout: &stdout, 261 Conn: conn, 262 vppBinapi: apiChannel, 263 vppStats: statsConn, 264 vppClient: vppClient, 265 } 266 } 267 268 func connectToBinAPI(t *testing.T, adapter adapter.VppAPI, vppCmd *exec.Cmd) (*govppcore.Connection, govppapi.Channel, *vppClient) { 269 connectRetry := func(retries int) (conn *govppcore.Connection, err error) { 270 for i := 1; i <= retries; i++ { 271 conn, err = govppcore.Connect(adapter) 272 if err != nil { 273 t.Logf("attempt #%d failed: %v, retrying in %v", i, err, vppConnectRetryDelay) 274 time.Sleep(vppConnectRetryDelay) 275 continue 276 } 277 return 278 } 279 return nil, fmt.Errorf("failed to connect after %d retries", retries) 280 } 281 282 // connect to binapi 283 conn, err := connectRetry(int(*vppRetry)) 284 if err != nil { 285 t.Errorf("connecting to VPP failed: %v", err) 286 if err := vppCmd.Process.Kill(); err != nil { 287 t.Fatalf("killing VPP failed: %v", err) 288 } 289 if state, err := vppCmd.Process.Wait(); err != nil { 290 t.Logf("VPP wait failed: %v", err) 291 } else { 292 t.Logf("VPP wait OK: %v", state) 293 } 294 t.FailNow() 295 } 296 297 apiChannel, err := conn.NewAPIChannel() 298 if err != nil { 299 t.Fatalf("creating channel failed: %v", err) 300 } 301 302 vppClient := &vppClient{ 303 t: t, 304 conn: conn, 305 ch: apiChannel, 306 } 307 return conn, apiChannel, vppClient 308 } 309 310 func (ctx *TestCtx) teardownVPP() { 311 disconnectBinAPI(ctx.t, ctx.Conn, ctx.vppBinapi, ctx.StatsConn) 312 stopVPP(ctx.t, ctx.vppCmd) 313 } 314 315 func disconnectBinAPI(t *testing.T, conn *govppcore.Connection, vppBinapi govppapi.Channel, 316 statsConn *govppcore.StatsConnection) { 317 // disconnect sometimes hangs 318 done := make(chan struct{}) 319 go func() { 320 if statsConn != nil { 321 statsConn.Disconnect() 322 } 323 vppBinapi.Close() 324 conn.Disconnect() 325 close(done) 326 }() 327 select { 328 case <-done: 329 time.Sleep(vppTermDelay) 330 case <-time.After(vppExitTimeout): 331 t.Logf("VPP disconnect timeout") 332 } 333 } 334 335 func stopVPP(t *testing.T, vppCmd *exec.Cmd) { 336 if err := vppCmd.Process.Signal(syscall.SIGTERM); err != nil { 337 t.Fatalf("sending SIGTERM to VPP failed: %v", err) 338 } 339 // wait until VPP exits 340 exit := make(chan struct{}) 341 go func() { 342 if err := vppCmd.Wait(); err != nil { 343 var exiterr *exec.ExitError 344 if errors.As(err, &exiterr) && strings.Contains(exiterr.Error(), "core dumped") { 345 t.Logf("VPP process CRASHED: %s", exiterr.Error()) 346 } else { 347 t.Logf("VPP process wait failed: %v", err) 348 } 349 } else { 350 t.Logf("VPP exit OK") 351 } 352 close(exit) 353 }() 354 select { 355 case <-exit: 356 // exited 357 case <-time.After(vppExitTimeout): 358 t.Logf("VPP exit timeout") 359 t.Logf("sending SIGKILL to VPP..") 360 if err := vppCmd.Process.Signal(syscall.SIGKILL); err != nil { 361 t.Fatalf("sending SIGKILL to VPP failed: %v", err) 362 } 363 } 364 } 365 366 type vppClient struct { 367 t *testing.T 368 conn *govppcore.Connection 369 ch govppapi.Channel 370 stats govppapi.StatsProvider 371 vpp vppcalls.VppCoreAPI 372 version vpp.Version 373 } 374 375 func (v *vppClient) NewAPIChannel() (govppapi.Channel, error) { 376 return v.conn.NewAPIChannel() 377 } 378 379 func (v *vppClient) NewStream(ctx context.Context, options ...govppapi.StreamOption) (govppapi.Stream, error) { 380 return v.conn.NewStream(ctx, options...) 381 } 382 383 func (v *vppClient) Invoke(ctx context.Context, req govppapi.Message, reply govppapi.Message) error { 384 return v.conn.Invoke(ctx, req, reply) 385 } 386 387 func (v *vppClient) WatchEvent(ctx context.Context, event govppapi.Message) (govppapi.Watcher, error) { 388 return v.conn.WatchEvent(ctx, event) 389 } 390 391 func (v *vppClient) Version() vpp.Version { 392 return v.version 393 } 394 395 func (v *vppClient) BinapiVersion() vpp.Version { 396 vppapiChan, err := v.conn.NewAPIChannel() 397 if err != nil { 398 v.t.Fatalf("Can't create new API channel (to get binary API version) due to: %v", err) 399 } 400 binapiVersion, err := binapi.CompatibleVersion(vppapiChan) 401 if err != nil { 402 v.t.Fatalf("Can't get binary API version due to: %v", err) 403 } 404 return binapiVersion 405 } 406 407 func (v *vppClient) CheckCompatiblity(msgs ...govppapi.Message) error { 408 return v.ch.CheckCompatiblity(msgs...) 409 } 410 411 func (v *vppClient) Stats() govppapi.StatsProvider { 412 return v.stats 413 } 414 415 func (v *vppClient) IsPluginLoaded(plugin string) bool { 416 ctx := context.Background() 417 plugins, err := v.vpp.GetPlugins(ctx) 418 if err != nil { 419 v.t.Fatalf("GetPlugins failed: %v", plugins) 420 } 421 for _, p := range plugins { 422 if p.Name == plugin { 423 return true 424 } 425 } 426 return false 427 } 428 429 func (v *vppClient) OnReconnect(h func()) { 430 // no-op 431 }