go.ligato.io/vpp-agent/v3@v3.5.0/plugins/govppmux/plugin_impl_govppmux.go (about) 1 // Copyright (c) 2021 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 govppmux 16 17 import ( 18 "context" 19 "encoding/gob" 20 "fmt" 21 "os" 22 "path" 23 "reflect" 24 "sort" 25 "strings" 26 "sync" 27 "time" 28 29 "github.com/pkg/errors" 30 "go.fd.io/govpp/adapter" 31 govppapi "go.fd.io/govpp/api" 32 govpp "go.fd.io/govpp/core" 33 "go.fd.io/govpp/proxy" 34 "go.ligato.io/cn-infra/v2/datasync/resync" 35 "go.ligato.io/cn-infra/v2/health/statuscheck" 36 "go.ligato.io/cn-infra/v2/infra" 37 "go.ligato.io/cn-infra/v2/logging" 38 "go.ligato.io/cn-infra/v2/rpc/rest" 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 _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2101" 45 _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2106" 46 _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2202" 47 _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2210" 48 ) 49 50 var ( 51 disabledSocketClient = os.Getenv("GOVPPMUX_NOSOCK") != "" 52 ) 53 54 // Plugin is the govppmux plugin implementation. 55 type Plugin struct { 56 Deps 57 58 config *Config 59 60 vpeHandler vppcalls.VppCoreAPI 61 62 binapiVersion vpp.Version 63 vppAdapter adapter.VppAPI 64 vppConn *govpp.Connection 65 vppConChan chan govpp.ConnectionEvent 66 lastConnErr error 67 vppapiChan govppapi.Channel 68 lastPID uint32 69 70 onnConnectMu sync.Mutex 71 onConnects []func() 72 73 statsMu sync.Mutex 74 statsAdapter adapter.StatsAPI 75 statsConn *govpp.StatsConnection 76 77 proxy *proxy.Server 78 79 // infoMu synchonizes access to fields 80 // vppInfo and lastEvent 81 infoMu sync.Mutex 82 vppInfo VPPInfo 83 lastEvent govpp.ConnectionEvent 84 85 cancel context.CancelFunc 86 wg sync.WaitGroup 87 } 88 89 // Deps defines dependencies for the govppmux plugin. 90 type Deps struct { 91 infra.PluginDeps 92 HTTPHandlers rest.HTTPHandlers 93 StatusCheck statuscheck.PluginStatusWriter 94 Resync *resync.Plugin 95 } 96 97 // Init is the entry point called by Agent Core. A single binary-API connection to VPP is established. 98 func (p *Plugin) Init() (err error) { 99 if p.config, err = p.loadConfig(); err != nil { 100 return err 101 } 102 p.Log.Debugf("config: %+v", p.config) 103 104 // set GoVPP config 105 govpp.HealthCheckProbeInterval = p.config.HealthCheckProbeInterval 106 govpp.HealthCheckReplyTimeout = p.config.HealthCheckReplyTimeout 107 govpp.HealthCheckThreshold = p.config.HealthCheckThreshold 108 govpp.DefaultReplyTimeout = p.config.ReplyTimeout 109 110 var address string 111 useShm := disabledSocketClient || p.config.ConnectViaShm || p.config.ShmPrefix != "" 112 if useShm { 113 address = p.config.ShmPrefix 114 } else { 115 address = p.config.BinAPISocketPath 116 } 117 118 p.Log.Debugf("found %d registered VPP handlers", len(vpp.GetHandlers())) 119 for name, handler := range vpp.GetHandlers() { 120 versions := handler.Versions() 121 p.Log.Debugf("- handler: %-10s has %d versions: %v", name, len(versions), versions) 122 } 123 124 // FIXME: this is a hack for GoVPP bug when register of the same message(same CRC and name) but different 125 // VPP version overwrites the already registered message from one VPP version (map key is only CRC+name 126 // and that didn't change with VPP version, but generated binapi generated 2 different go types for it). 127 // Similar fix exists also for integration tests. 128 if err := p.hackForBugInGoVPPMessageCache(address, useShm); err != nil { 129 return errors.Errorf("can't apply hack fixing bug in GoVPP "+ 130 "regarding stream's message type resolving: %v", err) 131 } 132 133 // TODO: Async connect & automatic reconnect support is not yet implemented in the agent, 134 // so synchronously wait until connected to VPP. 135 startTime := time.Now() 136 p.Log.Debugf("connecting to VPP..") 137 138 p.vppAdapter = NewVppAdapter(address, useShm) 139 p.vppConn, p.vppConChan, err = govpp.AsyncConnect(p.vppAdapter, p.config.RetryConnectCount, p.config.RetryConnectTimeout) 140 if err != nil { 141 return err 142 } 143 if err := p.waitForConnectionEvent(p.vppConChan); err != nil { 144 return err 145 } 146 took := time.Since(startTime) 147 p.Log.Debugf("connection to VPP established (took %s)", took.Round(time.Millisecond)) 148 149 if err := p.updateVPPInfo(); err != nil { 150 return errors.WithMessage(err, "retrieving VPP info failed") 151 } 152 153 // Connect to VPP status socket 154 var statsSocket string 155 if p.config.StatsSocketPath != "" { 156 statsSocket = p.config.StatsSocketPath 157 } else { 158 statsSocket = adapter.DefaultStatsSocket 159 } 160 statsAdapter := NewStatsAdapter(statsSocket) 161 if p.statsConn, err = govpp.ConnectStats(statsAdapter); err != nil { 162 p.Log.Warnf("Unable to connect to the VPP statistics socket, %v", err) 163 p.statsAdapter = nil 164 } 165 166 if p.config.ProxyEnabled { 167 // register binapi messages to gob package (required for proxy) 168 msgList := binapi.Versions[p.binapiVersion] 169 for _, msg := range msgList.AllMessages() { 170 gob.Register(msg) 171 } 172 err := p.startProxy(NewVppAdapter(address, useShm), NewStatsAdapter(statsSocket)) 173 if err != nil { 174 p.Log.Warnf("VPP proxy failed to start: %v", err) 175 } else { 176 p.Log.Infof("VPP proxy ready") 177 } 178 } 179 180 // register REST API handlers 181 p.registerHandlers(p.HTTPHandlers) 182 183 return nil 184 } 185 186 // waitForConnectionEvent waits for Connected event from govpp 187 func (p *Plugin) waitForConnectionEvent(vppConChan chan govpp.ConnectionEvent) error { 188 for { 189 event, ok := <-vppConChan 190 if !ok { 191 return errors.Errorf("VPP connection state channel closed") 192 } 193 if event.State == govpp.Connected { 194 break 195 } else if event.State == govpp.Failed || event.State == govpp.Disconnected { 196 return errors.Errorf("unable to establish connection to VPP (%v)", event.Error) 197 } else { 198 p.Log.Debugf("VPP connection state: %+v", event) 199 } 200 } 201 return nil 202 } 203 204 func (p *Plugin) hackForBugInGoVPPMessageCache(address string, useShm bool) error { 205 // connect to VPP 206 startTime := time.Now() 207 vppAdapter := NewVppAdapter(address, useShm) 208 vppConn, vppConChan, err := govpp.AsyncConnect(vppAdapter, p.config.RetryConnectCount, p.config.RetryConnectTimeout) 209 if err != nil { 210 return err 211 } 212 if err := p.waitForConnectionEvent(vppConChan); err != nil { 213 return err 214 } 215 took := time.Since(startTime) 216 p.Log.Debugf("first connection to VPP established (took %s)", took.Round(time.Millisecond)) 217 218 if vppConn == nil { 219 return fmt.Errorf("VPP connection is nil") 220 } 221 222 // detect binary API version 223 vppapiChan, err := vppConn.NewAPIChannel() 224 if err != nil { 225 return err 226 } 227 binapiVersion, err := binapi.CompatibleVersion(vppapiChan) 228 if err != nil { 229 return err 230 } 231 232 // overwrite messages with messages from correct VPP version 233 for _, msg := range binapi.Versions[binapiVersion].AllMessages() { 234 reRegisterMessage(msg) 235 } 236 237 // disconnect from VPP (GoVPP is caching the messages that we want to override 238 // by first connection to VPP -> we must disconnect and reconnect later again) 239 vppConn.Disconnect() 240 241 return nil 242 } 243 244 // reRegisterMessage overwrites the original registration of Messages in GoVPP with new message registration. 245 func reRegisterMessage(x govppapi.Message) { 246 typ := reflect.TypeOf(x) 247 namecrc := x.GetMessageName() + "_" + x.GetCrcString() 248 binapiPath := path.Dir(reflect.TypeOf(x).Elem().PkgPath()) 249 govppapi.GetRegisteredMessages()[binapiPath][namecrc] = x 250 govppapi.GetRegisteredMessageTypes()[binapiPath][typ] = namecrc 251 } 252 253 // AfterInit reports status check. 254 func (p *Plugin) AfterInit() error { 255 // Register providing status reports (push mode) 256 p.StatusCheck.Register(p.PluginName, nil) 257 p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil) 258 259 var ctx context.Context 260 ctx, p.cancel = context.WithCancel(context.Background()) 261 262 p.wg.Add(1) 263 go p.handleVPPConnectionEvents(ctx) 264 265 return nil 266 } 267 268 // Close cleans up the resources allocated by the govppmux plugin. 269 func (p *Plugin) Close() error { 270 p.cancel() 271 p.wg.Wait() 272 273 defer func() { 274 if p.vppConn != nil { 275 p.vppConn.Disconnect() 276 } 277 if p.statsAdapter != nil { 278 if err := p.statsAdapter.Disconnect(); err != nil { 279 p.Log.Errorf("VPP statistics socket adapter disconnect error: %v", err) 280 } 281 } 282 }() 283 284 if p.proxy != nil { 285 p.proxy.DisconnectBinapi() 286 p.proxy.DisconnectStats() 287 } 288 289 return nil 290 } 291 292 func (p *Plugin) Version() vpp.Version { 293 return p.binapiVersion 294 } 295 296 func (p *Plugin) CheckCompatiblity(msgs ...govppapi.Message) error { 297 p.infoMu.Lock() 298 defer p.infoMu.Unlock() 299 if p.vppapiChan == nil { 300 apiChan, err := p.vppConn.NewAPIChannel() 301 if err != nil { 302 return err 303 } 304 p.vppapiChan = apiChan 305 } 306 return p.vppapiChan.CheckCompatiblity(msgs...) 307 } 308 309 func (p *Plugin) Stats() govppapi.StatsProvider { 310 if p.statsConn == nil { 311 return nil 312 } 313 return p 314 } 315 316 func (p *Plugin) BinapiVersion() vpp.Version { 317 return p.binapiVersion 318 } 319 320 // VPPInfo returns information about VPP session. 321 func (p *Plugin) VPPInfo() VPPInfo { 322 p.infoMu.Lock() 323 defer p.infoMu.Unlock() 324 return p.vppInfo 325 } 326 327 // IsPluginLoaded returns true if plugin is loaded. 328 func (p *Plugin) IsPluginLoaded(plugin string) bool { 329 p.infoMu.Lock() 330 defer p.infoMu.Unlock() 331 for _, p := range p.vppInfo.Plugins { 332 if p.Name == plugin { 333 return true 334 } 335 } 336 return false 337 } 338 339 func (p *Plugin) updateVPPInfo() (err error) { 340 if p.vppConn == nil { 341 return fmt.Errorf("VPP connection is nil") 342 } 343 344 p.vppapiChan, err = p.vppConn.NewAPIChannel() 345 if err != nil { 346 return err 347 } 348 p.binapiVersion, err = binapi.CompatibleVersion(p.vppapiChan) 349 if err != nil { 350 return err 351 } 352 353 p.vpeHandler, err = vppcalls.NewHandler(p) 354 if err != nil { 355 return errors.New("no compatible VPP handler found") 356 } 357 358 ctx := context.TODO() 359 360 version, err := p.vpeHandler.RunCli(ctx, "show version verbose") 361 if err != nil { 362 p.Log.Warnf("RunCli error: %v", err) 363 } else { 364 p.Log.Debugf("vpp# show version verbose\n%s", version) 365 } 366 cmdline, err := p.vpeHandler.RunCli(ctx, "show version cmdline") 367 if err != nil { 368 p.Log.Warnf("RunCli error: %v", err) 369 } else { 370 out := strings.Replace(cmdline, "\n", "", -1) 371 p.Log.Debugf("vpp# show version cmdline:\n%s", out) 372 } 373 374 ver, err := p.vpeHandler.GetVersion(ctx) 375 if err != nil { 376 return err 377 } 378 session, err := p.vpeHandler.GetSession(ctx) 379 if err != nil { 380 return err 381 } 382 p.Log.WithFields(logging.Fields{ 383 "PID": session.PID, 384 "ClientID": session.ClientIdx, 385 }).Infof("VPP version: %v", ver.Version) 386 387 modules, err := p.vpeHandler.GetModules(ctx) 388 if err != nil { 389 return err 390 } 391 p.Log.Debugf("VPP has %d core modules: %v", len(modules), modules) 392 393 plugins, err := p.vpeHandler.GetPlugins(ctx) 394 if err != nil { 395 return err 396 } 397 // sort plugins by name 398 sort.Slice(plugins, func(i, j int) bool { return plugins[i].Name < plugins[j].Name }) 399 400 p.Log.Debugf("VPP loaded %d plugins", len(plugins)) 401 for _, plugin := range plugins { 402 p.Log.Debugf(" - plugin: %v", plugin) 403 } 404 405 p.infoMu.Lock() 406 p.vppInfo = VPPInfo{ 407 Connected: true, 408 VersionInfo: *ver, 409 SessionInfo: *session, 410 Plugins: plugins, 411 } 412 p.infoMu.Unlock() 413 414 if p.lastPID != 0 && p.lastPID != session.PID { 415 p.Log.Warnf("VPP has restarted (previous PID: %d)", p.lastPID) 416 p.onConnect() 417 } 418 p.lastPID = session.PID 419 420 return nil 421 } 422 423 func (p *Plugin) OnReconnect(fn func()) { 424 p.onnConnectMu.Lock() 425 defer p.onnConnectMu.Unlock() 426 p.onConnects = append(p.onConnects, fn) 427 } 428 429 func (p *Plugin) onConnect() { 430 p.onnConnectMu.Lock() 431 defer p.onnConnectMu.Unlock() 432 for _, fn := range p.onConnects { 433 fn() 434 } 435 } 436 437 // handleVPPConnectionEvents handles VPP connection events. 438 func (p *Plugin) handleVPPConnectionEvents(ctx context.Context) { 439 defer p.wg.Done() 440 441 for { 442 select { 443 case event, ok := <-p.vppConChan: 444 if !ok { 445 p.lastConnErr = errors.Errorf("VPP connection state channel closed") 446 p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.Error, p.lastConnErr) 447 return 448 } 449 450 p.Log.Debugf("VPP connection state changed: %+v", event) 451 452 if event.State == govpp.Connected { 453 if err := p.updateVPPInfo(); err != nil { 454 p.Log.Errorf("updating VPP info failed: %v", err) 455 } 456 457 if p.config.ReconnectResync && p.lastConnErr != nil { 458 p.Log.Info("Starting resync after VPP reconnect") 459 if p.Resync != nil { 460 p.Resync.DoResync() 461 p.lastConnErr = nil 462 } else { 463 p.Log.Warn("Expected resync after VPP reconnect could not start because of missing Resync plugin") 464 } 465 } 466 p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.OK, nil) 467 } else if event.State == govpp.Failed || event.State == govpp.Disconnected { 468 p.infoMu.Lock() 469 p.vppInfo.Connected = false 470 p.infoMu.Unlock() 471 472 p.lastConnErr = errors.Errorf("VPP connection lost (event: %+v)", event) 473 p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.Error, p.lastConnErr) 474 475 // TODO: fix reconnecting after reaching maximum reconnect attempts 476 // current implementation wont work with already created govpp channels 477 // keep reconnecting 478 /*if event.State == govpp.Failed { 479 p.vppConn, p.vppConChan, _ = govpp.AsyncConnect(p.vppAdapter, p.config.RetryConnectCount, p.config.RetryConnectTimeout) 480 }*/ 481 } else if event.State == govpp.NotResponding { 482 p.infoMu.Lock() 483 p.vppInfo.Connected = false 484 p.infoMu.Unlock() 485 486 p.lastConnErr = errors.Errorf("VPP is not responding (event: %+v)", event) 487 p.StatusCheck.ReportStateChange(p.PluginName, statuscheck.Error, p.lastConnErr) 488 } else { 489 p.Log.Warnf("unknown VPP connection state: %+v", event) 490 } 491 492 p.infoMu.Lock() 493 p.lastEvent = event 494 p.infoMu.Unlock() 495 496 case <-ctx.Done(): 497 return 498 } 499 } 500 } 501 502 func (p *Plugin) startProxy(vppapi adapter.VppAPI, statsapi adapter.StatsAPI) (err error) { 503 p.Log.Debugf("starting VPP proxy") 504 505 p.proxy, err = proxy.NewServer() 506 if err != nil { 507 return errors.Wrap(err, "creating proxy failed") 508 } 509 if err = p.proxy.ConnectBinapi(vppapi); err != nil { 510 return errors.Wrap(err, "connecting binapi for proxy failed") 511 } 512 if err = p.proxy.ConnectStats(statsapi); err != nil { 513 return errors.Wrap(err, "connecting stats for proxy failed") 514 } 515 return nil 516 }