go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/ifplugin/interface_state.go (about) 1 // Copyright (c) 2017 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 ifplugin 16 17 import ( 18 "context" 19 "os" 20 "sync" 21 "time" 22 23 govppapi "go.fd.io/govpp/api" 24 "go.ligato.io/cn-infra/v2/logging" 25 26 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 27 "go.ligato.io/vpp-agent/v3/plugins/vpp" 28 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" 29 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" 30 intf "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 31 ) 32 33 var ( 34 debugIfStates = os.Getenv("DEBUG_IFSTATES") != "" 35 ) 36 37 // InterfaceStateUpdater holds state data of all VPP interfaces. 38 type InterfaceStateUpdater struct { 39 log logging.Logger 40 41 kvScheduler kvs.KVScheduler 42 swIfIndexes ifaceidx.IfaceMetadataIndex 43 publishIfState func(notification *intf.InterfaceNotification) 44 45 // access guards access to ifState map 46 access sync.Mutex 47 ifState map[uint32]*intf.InterfaceState // swIfIndex 48 49 vppClient vpp.Client 50 51 ifMetaChan chan ifaceidx.IfaceMetadataDto 52 53 ifHandler vppcalls.InterfaceVppAPI 54 ifEvents chan *vppcalls.InterfaceEvent 55 cancelIfEvents func() 56 57 ifsForUpdate map[uint32]struct{} 58 lastIfCounters map[uint32]govppapi.InterfaceCounters 59 ifStats govppapi.InterfaceStats 60 61 lastIfNotif time.Time 62 lastIfMeta time.Time 63 64 ctx context.Context 65 cancel context.CancelFunc // cancel can be used to cancel all goroutines and their jobs inside of the plugin 66 wg sync.WaitGroup // wait group that allows to wait until all goroutines of the plugin have finished 67 } 68 69 // Init members (channels, maps...) and start go routines 70 func (c *InterfaceStateUpdater) Init( 71 ctx context.Context, 72 logger logging.PluginLogger, 73 kvScheduler kvs.KVScheduler, 74 vppClient vpp.Client, 75 swIfIndexes ifaceidx.IfaceMetadataIndex, 76 publishIfState func(*intf.InterfaceNotification), 77 readCounters bool, 78 ) error { 79 c.log = logger.NewLogger("if-state") 80 81 // Mappings 82 c.swIfIndexes = swIfIndexes 83 84 c.vppClient = vppClient 85 c.kvScheduler = kvScheduler 86 c.publishIfState = publishIfState 87 c.ifState = make(map[uint32]*intf.InterfaceState) 88 89 c.ifsForUpdate = make(map[uint32]struct{}) 90 c.lastIfCounters = make(map[uint32]govppapi.InterfaceCounters) 91 92 // Init handlers 93 c.ifHandler = vppcalls.CompatibleInterfaceVppHandler(c.vppClient, logger.NewLogger("if-handler")) 94 95 c.ifMetaChan = make(chan ifaceidx.IfaceMetadataDto, 1000) 96 swIfIndexes.WatchInterfaces("ifplugin_ifstate", c.ifMetaChan) 97 98 c.ifEvents = make(chan *vppcalls.InterfaceEvent, 1000) 99 100 // Create child context 101 c.ctx, c.cancel = context.WithCancel(ctx) 102 103 // Watch for incoming notifications 104 c.wg.Add(1) 105 go c.watchVPPNotifications(c.ctx) 106 107 // Periodically read VPP counters and combined counters for VPP statistics 108 if disableInterfaceStats { 109 c.log.Warnf("reading interface stats is DISABLED!") 110 } else if readCounters { 111 c.wg.Add(1) 112 go c.startReadingCounters(c.ctx) 113 } 114 115 if disableStatusPublishing { 116 c.log.Warnf("publishing interface status is DISABLED!") 117 } else { 118 c.wg.Add(1) 119 go c.startUpdatingIfStateDetails(c.ctx) 120 } 121 122 return nil 123 } 124 125 // AfterInit subscribes for watching VPP notifications on previously initialized channel 126 func (c *InterfaceStateUpdater) AfterInit() error { 127 if err := c.subscribeVPPNotifications(c.ctx); err != nil { 128 return err 129 } 130 c.vppClient.OnReconnect(func() { 131 c.cancelIfEvents() 132 if err := c.subscribeVPPNotifications(c.ctx); err != nil { 133 c.log.Warnf("WatchInterfaceEvents failed: %v", err) 134 } 135 }) 136 return nil 137 } 138 139 // Close unsubscribes from interface state notifications from VPP & GOVPP channel 140 func (c *InterfaceStateUpdater) Close() error { 141 c.cancel() 142 c.wg.Wait() 143 return nil 144 } 145 146 // subscribeVPPNotifications subscribes for interface state notifications from VPP. 147 func (c *InterfaceStateUpdater) subscribeVPPNotifications(ctx context.Context) error { 148 ctx, c.cancelIfEvents = context.WithCancel(ctx) 149 if err := c.ifHandler.WatchInterfaceEvents(ctx, c.ifEvents); err != nil { 150 return err 151 } 152 return nil 153 } 154 155 // watchVPPNotifications watches for delivery of notifications from VPP. 156 func (c *InterfaceStateUpdater) watchVPPNotifications(ctx context.Context) { 157 defer c.wg.Done() 158 159 for { 160 select { 161 case notif := <-c.ifEvents: 162 // if the notification is a result of a configuration change, 163 // make sure the associated transaction has already finalized 164 c.kvScheduler.TransactionBarrier() 165 166 c.processIfStateEvent(notif) 167 168 case ifMetaDto := <-c.ifMetaChan: 169 if ifMetaDto.Del { 170 c.setIfStateDeleted(ifMetaDto.Metadata.SwIfIndex, ifMetaDto.Name) 171 } else if !ifMetaDto.Update { 172 c.processIfMetaCreate(ifMetaDto.Metadata.SwIfIndex) 173 } 174 175 case <-ctx.Done(): 176 // stop watching for notifications and periodic statistics reader 177 c.log.Debug("Interface state VPP notification watcher stopped") 178 return 179 } 180 } 181 } 182 183 func (c *InterfaceStateUpdater) startUpdatingIfStateDetails(ctx context.Context) { 184 defer c.wg.Done() 185 186 /*timer := time.NewTimer(PeriodicPollingPeriod) 187 if !ifUpdateTimer.Stop() { 188 <-ifUpdateTimer.C 189 } 190 ifUpdateTimer.Reset(PeriodicPollingPeriod)*/ 191 192 tick := time.NewTicker(StateUpdateDelay) 193 for { 194 select { 195 case <-tick.C: 196 c.doUpdatesIfStateDetails() 197 198 case <-ctx.Done(): 199 c.log.Debug("update if state details polling stopped") 200 return 201 } 202 } 203 } 204 205 // startReadingCounters periodically reads statistics for all interfaces 206 func (c *InterfaceStateUpdater) startReadingCounters(ctx context.Context) { 207 defer c.wg.Done() 208 209 tick := time.NewTicker(PeriodicPollingPeriod) 210 for { 211 select { 212 case <-tick.C: 213 statsClient := c.vppClient.Stats() 214 if statsClient == nil { 215 c.log.Warnf("VPP stats client not available") 216 // TODO: use retry with backoff instead of returning here 217 return 218 } 219 c.doInterfaceStatsRead(statsClient) 220 221 case <-ctx.Done(): 222 c.log.Debug("Interface state VPP periodic polling stopped") 223 return 224 } 225 } 226 } 227 228 func (c *InterfaceStateUpdater) processIfMetaCreate(swIfIdx uint32) { 229 c.access.Lock() 230 defer c.access.Unlock() 231 232 c.lastIfMeta = time.Now() 233 234 c.ifsForUpdate[swIfIdx] = struct{}{} 235 } 236 237 func (c *InterfaceStateUpdater) doUpdatesIfStateDetails() { 238 c.access.Lock() 239 240 // prevent reading stats if last interface notification has been 241 // received in less than polling period 242 if time.Since(c.lastIfMeta) < StateUpdateDelay { 243 c.access.Unlock() 244 return 245 } 246 if len(c.ifsForUpdate) == 0 { 247 c.access.Unlock() 248 return 249 } 250 251 c.log.Debugf("updating interface states for %d interfaces", len(c.ifsForUpdate)) 252 253 var ifIdxs []uint32 254 if len(c.ifsForUpdate) < 1000 { 255 for ifIdx := range c.ifsForUpdate { 256 ifIdxs = append(ifIdxs, ifIdx) 257 } 258 } 259 // clear interfaces for update 260 c.ifsForUpdate = make(map[uint32]struct{}) 261 262 // we dont want to lock during potentially long dump call 263 c.access.Unlock() 264 265 ifaces, err := c.ifHandler.DumpInterfaceStates(ifIdxs...) 266 if err != nil { 267 c.log.Warnf("dumping interface states failed: %v", err) 268 return 269 } 270 271 c.access.Lock() 272 for _, ifaceDetails := range ifaces { 273 if ifaceDetails == nil { 274 // this interface was removed and the updater hasn't yet received notification 275 continue 276 } 277 c.updateIfStateDetails(ifaceDetails) 278 } 279 c.access.Unlock() 280 } 281 282 // doInterfaceStatsRead dumps statistics using interface filter and processes them 283 func (c *InterfaceStateUpdater) doInterfaceStatsRead(statsClient govppapi.StatsProvider) { 284 c.access.Lock() 285 defer c.access.Unlock() 286 287 // prevent reading stats if last interface notification has been 288 // received in less than polling period 289 if time.Since(c.lastIfNotif) < StateUpdateDelay { 290 return 291 } 292 293 err := statsClient.GetInterfaceStats(&c.ifStats) 294 if err != nil { 295 // TODO add some counter to prevent it log forever 296 c.log.Errorf("failed to read statistics data: %v", err) 297 } 298 if len(c.ifStats.Interfaces) == 0 { 299 return 300 } 301 302 for i, ifCounters := range c.ifStats.Interfaces { 303 index := uint32(i) 304 if last, ok := c.lastIfCounters[index]; ok && last == ifCounters { 305 continue 306 } 307 c.lastIfCounters[index] = ifCounters 308 c.processInterfaceStatEntry(ifCounters) 309 } 310 } 311 312 // processInterfaceStatEntry fills state data for every registered interface and publishes them 313 func (c *InterfaceStateUpdater) processInterfaceStatEntry(ifCounters govppapi.InterfaceCounters) { 314 ifState, found := c.getIfStateDataWLookup(ifCounters.InterfaceIndex) 315 if !found { 316 return 317 } 318 319 ifState.Statistics = &intf.InterfaceState_Statistics{ 320 DropPackets: ifCounters.Drops, 321 PuntPackets: ifCounters.Punts, 322 Ipv4Packets: ifCounters.IP4, 323 Ipv6Packets: ifCounters.IP6, 324 InNobufPackets: ifCounters.RxNoBuf, 325 InMissPackets: ifCounters.RxMiss, 326 InErrorPackets: ifCounters.RxErrors, 327 OutErrorPackets: ifCounters.TxErrors, 328 InPackets: ifCounters.Rx.Packets, 329 InBytes: ifCounters.Rx.Bytes, 330 OutPackets: ifCounters.Tx.Packets, 331 OutBytes: ifCounters.Tx.Bytes, 332 } 333 334 c.publishIfState(&intf.InterfaceNotification{ 335 Type: intf.InterfaceNotification_COUNTERS, 336 State: ifState, 337 }) 338 } 339 340 // processIfStateEvent process a VPP state event notification. 341 func (c *InterfaceStateUpdater) processIfStateEvent(notif *vppcalls.InterfaceEvent) { 342 c.access.Lock() 343 defer c.access.Unlock() 344 345 c.lastIfNotif = time.Now() 346 347 // update and return if state data 348 ifState, found := c.updateIfStateFlags(notif) 349 if !found { 350 return 351 } 352 353 if debugIfStates { 354 c.log.Debugf("Interface state notification for %s (idx: %d): %+v", 355 ifState.Name, ifState.IfIndex, notif) 356 } 357 358 // store data in ETCD 359 c.publishIfState(&intf.InterfaceNotification{ 360 Type: intf.InterfaceNotification_UPDOWN, 361 State: ifState, 362 }) 363 } 364 365 // getIfStateData returns interface state data structure for the specified interface index and interface name. 366 // NOTE: plugin.ifStateData needs to be locked when calling this function! 367 func (c *InterfaceStateUpdater) getIfStateData(swIfIndex uint32, ifName string) (*intf.InterfaceState, bool) { 368 ifState, ok := c.ifState[swIfIndex] 369 // check also if the provided logical name c the same as the one associated 370 // with swIfIndex, because swIfIndexes might be reused 371 if ok && ifState.Name == ifName { 372 return ifState, true 373 } 374 return nil, false 375 } 376 377 // getIfStateDataWLookup returns interface state data structure for the specified interface index (creates it if it does not exist). 378 // NOTE: plugin.ifStateData needs to be locked when calling this function! 379 func (c *InterfaceStateUpdater) getIfStateDataWLookup(ifIdx uint32) (*intf.InterfaceState, bool) { 380 ifName, _, found := c.swIfIndexes.LookupBySwIfIndex(ifIdx) 381 if !found { 382 return nil, found 383 } 384 385 ifState, found := c.getIfStateData(ifIdx, ifName) 386 if !found { 387 ifState = &intf.InterfaceState{ 388 IfIndex: ifIdx, 389 Name: ifName, 390 Statistics: &intf.InterfaceState_Statistics{}, 391 } 392 c.ifState[ifIdx] = ifState 393 found = true 394 } 395 396 return ifState, found 397 } 398 399 // updateIfStateFlags updates the interface state data in memory from provided VPP flags message and returns updated state data. 400 // NOTE: plugin.ifStateData needs to be locked when calling this function! 401 func (c *InterfaceStateUpdater) updateIfStateFlags(vppMsg *vppcalls.InterfaceEvent) ( 402 iface *intf.InterfaceState, found bool, 403 ) { 404 ifState, found := c.getIfStateDataWLookup(vppMsg.SwIfIndex) 405 if !found { 406 return nil, false 407 } 408 ifState.LastChange = time.Now().Unix() 409 410 if vppMsg.Deleted { 411 ifState.AdminStatus = intf.InterfaceState_DELETED 412 ifState.OperStatus = intf.InterfaceState_DELETED 413 } else { 414 if vppMsg.AdminState == 1 { 415 ifState.AdminStatus = intf.InterfaceState_UP 416 } else { 417 ifState.AdminStatus = intf.InterfaceState_DOWN 418 } 419 if vppMsg.LinkState == 1 { 420 ifState.OperStatus = intf.InterfaceState_UP 421 } else { 422 ifState.OperStatus = intf.InterfaceState_DOWN 423 } 424 } 425 return ifState, true 426 } 427 428 // updateIfStateDetails updates the interface state data in memory from provided VPP details message. 429 func (c *InterfaceStateUpdater) updateIfStateDetails(ifDetails *vppcalls.InterfaceState) { 430 ifState, found := c.getIfStateDataWLookup(ifDetails.SwIfIndex) 431 if !found { 432 return 433 } 434 435 ifState.InternalName = ifDetails.InternalName 436 ifState.PhysAddress = ifDetails.PhysAddress.String() 437 ifState.AdminStatus = ifDetails.AdminState 438 ifState.OperStatus = ifDetails.LinkState 439 ifState.Speed = ifDetails.LinkSpeed 440 ifState.Duplex = ifDetails.LinkDuplex 441 ifState.Mtu = uint32(ifDetails.LinkMTU) 442 443 c.publishIfState(&intf.InterfaceNotification{State: ifState}) 444 } 445 446 // setIfStateDeleted marks the interface as deleted in the state data structure in memory. 447 func (c *InterfaceStateUpdater) setIfStateDeleted(swIfIndex uint32, ifName string) { 448 c.access.Lock() 449 defer c.access.Unlock() 450 451 ifState, found := c.getIfStateData(swIfIndex, ifName) 452 if !found { 453 return 454 } 455 ifState.AdminStatus = intf.InterfaceState_DELETED 456 ifState.OperStatus = intf.InterfaceState_DELETED 457 ifState.LastChange = time.Now().Unix() 458 459 // this can be post-processed by multiple plugins 460 c.publishIfState(&intf.InterfaceNotification{State: ifState}) 461 }