go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/ifplugin/ifplugin.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 //go:generate descriptor-adapter --descriptor-name Interface --value-type *vpp_interfaces.Interface --meta-type *ifaceidx.IfaceMetadata --import "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 16 //go:generate descriptor-adapter --descriptor-name Unnumbered --value-type *vpp_interfaces.Interface_Unnumbered --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 17 //go:generate descriptor-adapter --descriptor-name RxMode --value-type *vpp_interfaces.Interface --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 18 //go:generate descriptor-adapter --descriptor-name RxPlacement --value-type *vpp_interfaces.Interface_RxPlacement --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 19 //go:generate descriptor-adapter --descriptor-name BondedInterface --value-type *vpp_interfaces.BondLink_BondedInterface --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 20 //go:generate descriptor-adapter --descriptor-name Span --value-type *vpp_interfaces.Span --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 21 //go:generate descriptor-adapter --descriptor-name IP6ND --value-type *vpp_interfaces.Interface_IP6ND --import "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" --output-dir "descriptor" 22 23 package ifplugin 24 25 import ( 26 "context" 27 "sync" 28 "time" 29 30 "github.com/pkg/errors" 31 "go.ligato.io/cn-infra/v2/datasync" 32 "go.ligato.io/cn-infra/v2/health/statuscheck" 33 "go.ligato.io/cn-infra/v2/idxmap" 34 "go.ligato.io/cn-infra/v2/infra" 35 "go.ligato.io/cn-infra/v2/servicelabel" 36 "go.ligato.io/cn-infra/v2/utils/safeclose" 37 38 "go.ligato.io/vpp-agent/v3/plugins/govppmux" 39 "go.ligato.io/vpp-agent/v3/plugins/kvscheduler" 40 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 41 linux_ifcalls "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/linuxcalls" 42 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin" 43 "go.ligato.io/vpp-agent/v3/plugins/netalloc" 44 vppclient "go.ligato.io/vpp-agent/v3/plugins/vpp" 45 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/descriptor" 46 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" 47 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" 48 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp" 49 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 50 51 _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2101" 52 _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2106" 53 _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2202" 54 _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2210" 55 ) 56 57 func init() { 58 kvscheduler.AddNonRetryableError(vppclient.ErrPluginDisabled) 59 kvscheduler.AddNonRetryableError(vppcalls.ErrIPIPUnsupported) 60 } 61 62 // Default Go routine count used while retrieving linux configuration 63 const goRoutineCount = 10 64 65 // IfPlugin configures VPP interfaces using GoVPP. 66 type IfPlugin struct { 67 Deps 68 69 // handlers 70 ifHandler vppcalls.InterfaceVppAPI 71 linuxIfHandler linux_ifcalls.NetlinkAPIRead 72 73 // index maps 74 intfIndex ifaceidx.IfaceMetadataIndex 75 dhcpIndex idxmap.NamedMapping 76 77 // descriptors 78 linkStateDescriptor *descriptor.LinkStateDescriptor 79 dhcpDescriptor *descriptor.DHCPDescriptor 80 81 // from config file 82 defaultMtu uint32 83 84 // state data 85 publishStats bool 86 publishLock sync.Mutex 87 statusCheckReg bool 88 watchStatusReg datasync.WatchRegistration 89 resyncStatusChan chan datasync.ResyncEvent 90 ifStateChan chan *interfaces.InterfaceNotification 91 ifStateUpdater *InterfaceStateUpdater 92 93 // go routine management 94 ctx context.Context 95 cancel context.CancelFunc 96 wg sync.WaitGroup 97 } 98 99 // Deps lists dependencies of the interface plugin. 100 type Deps struct { 101 infra.PluginDeps 102 KVScheduler kvs.KVScheduler 103 VPP govppmux.API 104 ServiceLabel servicelabel.ReaderAPI 105 AddrAlloc netalloc.AddressAllocator 106 /* LinuxIfPlugin and NsPlugin deps are optional, 107 but they are required if AFPacket or TAP+TAP_TO_VPP interfaces are used. */ 108 LinuxIfPlugin descriptor.LinuxPluginAPI 109 NsPlugin nsplugin.API 110 // state publishing 111 StatusCheck statuscheck.PluginStatusWriter 112 PublishErrors datasync.KeyProtoValWriter // TODO: to be used with a generic plugin for publishing errors (not just interfaces and BDs) 113 Watcher datasync.KeyValProtoWatcher /* for resync of interface state data (PublishStatistics) */ 114 NotifyStates datasync.KeyProtoValWriter /* e.g. Kafka (up/down events only)*/ 115 PublishStatistics datasync.KeyProtoValWriter /* e.g. ETCD (with resync) */ 116 DataSyncs map[string]datasync.KeyProtoValWriter /* available DBs for PublishStatistics */ 117 PushNotification func(notification *vpp.Notification) 118 } 119 120 // Init loads configuration file and registers interface-related descriptors. 121 func (p *IfPlugin) Init() (err error) { 122 // Create plugin context, save cancel function into the plugin handle. 123 p.ctx, p.cancel = context.WithCancel(context.Background()) 124 125 // Read config file and set all related fields 126 if err := p.fromConfigFile(); err != nil { 127 return err 128 } 129 130 // Fills nil dependencies with default values 131 p.publishStats = p.PublishStatistics != nil || p.NotifyStates != nil 132 p.fixNilPointers() 133 134 // Init handlers 135 p.ifHandler = vppcalls.CompatibleInterfaceVppHandler(p.VPP, p.Log) 136 if p.ifHandler == nil { 137 return errors.New("interface VPP handler is not available") 138 } 139 140 if p.LinuxIfPlugin != nil { 141 p.linuxIfHandler = linux_ifcalls.NewNetLinkHandler( 142 p.NsPlugin, 143 p.LinuxIfPlugin.GetInterfaceIndex(), 144 p.ServiceLabel.GetAgentPrefix(), 145 goRoutineCount, p.Log, 146 ) 147 } 148 149 // Init descriptors 150 151 // -> base interface descriptor 152 ifaceDescriptor, ifaceDescrCtx := descriptor.NewInterfaceDescriptor(p.ifHandler, 153 p.AddrAlloc, p.defaultMtu, p.linuxIfHandler, p.LinuxIfPlugin, p.NsPlugin, p.Log) 154 err = p.KVScheduler.RegisterKVDescriptor(ifaceDescriptor) 155 if err != nil { 156 return err 157 } 158 var withIndex bool 159 metadataMap := p.KVScheduler.GetMetadataMap(ifaceDescriptor.Name) 160 p.intfIndex, withIndex = metadataMap.(ifaceidx.IfaceMetadataIndex) 161 if !withIndex { 162 return errors.New("missing index with interface metadata") 163 } 164 ifaceDescrCtx.SetInterfaceIndex(p.intfIndex) 165 166 // -> descriptors for derived values / notifications 167 var ( 168 linkStateDescriptor *kvs.KVDescriptor 169 dhcpDescriptor *kvs.KVDescriptor 170 ) 171 dhcpDescriptor, p.dhcpDescriptor = descriptor.NewDHCPDescriptor(p.KVScheduler, 172 p.ifHandler, p.intfIndex, p.Log) 173 linkStateDescriptor, p.linkStateDescriptor = descriptor.NewLinkStateDescriptor( 174 p.KVScheduler, p.ifHandler, p.intfIndex, p.Log) 175 176 rxModeDescriptor := descriptor.NewRxModeDescriptor(p.ifHandler, p.intfIndex, p.Log) 177 rxPlacementDescriptor := descriptor.NewRxPlacementDescriptor(p.ifHandler, p.intfIndex, p.Log) 178 addrDescriptor := descriptor.NewInterfaceAddressDescriptor(p.ifHandler, p.AddrAlloc, p.intfIndex, p.Log) 179 unIfDescriptor := descriptor.NewUnnumberedIfDescriptor(p.ifHandler, p.intfIndex, p.Log) 180 bondIfDescriptor, _ := descriptor.NewBondedInterfaceDescriptor(p.ifHandler, p.intfIndex, p.Log) 181 vrfDescriptor := descriptor.NewInterfaceVrfDescriptor(p.ifHandler, p.intfIndex, p.Log) 182 withAddrDescriptor := descriptor.NewInterfaceWithAddrDescriptor(p.Log) 183 spanDescriptor, spanDescriptorCtx := descriptor.NewSpanDescriptor(p.ifHandler, p.Log) 184 spanDescriptorCtx.SetInterfaceIndex(p.intfIndex) 185 ip6ndDescriptor := descriptor.NewIP6ndDescriptor(p.KVScheduler, p.ifHandler, p.intfIndex, p.Log) 186 187 err = p.KVScheduler.RegisterKVDescriptor( 188 dhcpDescriptor, 189 linkStateDescriptor, 190 rxModeDescriptor, 191 rxPlacementDescriptor, 192 addrDescriptor, 193 unIfDescriptor, 194 bondIfDescriptor, 195 vrfDescriptor, 196 withAddrDescriptor, 197 spanDescriptor, 198 ip6ndDescriptor, 199 ) 200 if err != nil { 201 return err 202 } 203 204 // start watching for DHCP notifications 205 p.dhcpIndex = p.KVScheduler.GetMetadataMap(dhcpDescriptor.Name) 206 if p.dhcpIndex == nil { 207 return errors.New("missing index with DHCP metadata") 208 } 209 p.dhcpDescriptor.WatchDHCPNotifications(p.ctx) 210 211 // interface state data 212 if p.publishStats { 213 // subscribe & watch for resync of interface state data 214 p.resyncStatusChan = make(chan datasync.ResyncEvent) 215 216 p.wg.Add(1) 217 go p.watchStatusEvents() 218 } 219 220 // start interface state updater 221 p.ifStateChan = make(chan *interfaces.InterfaceNotification, 1000) 222 223 // start interface state publishing 224 p.wg.Add(1) 225 go p.publishIfStateEvents() 226 227 // Interface state updater 228 p.ifStateUpdater = &InterfaceStateUpdater{} 229 230 var n int 231 var t time.Time 232 ifNotifHandler := func(state *interfaces.InterfaceNotification) { 233 select { 234 case p.ifStateChan <- state: 235 // OK 236 default: 237 // full 238 if time.Since(t) > time.Second { 239 p.Log.Debugf("ifStateChan channel is full (%d)", n) 240 n = 0 241 } else { 242 n++ 243 } 244 t = time.Now() 245 } 246 } 247 248 err = p.ifStateUpdater.Init(p.ctx, p.Log, p.KVScheduler, p.VPP, p.intfIndex, ifNotifHandler, p.publishStats) 249 if err != nil { 250 return err 251 } 252 253 if p.publishStats { 254 if err = p.subscribeWatcher(); err != nil { 255 return err 256 } 257 } 258 259 return nil 260 } 261 262 func (p *IfPlugin) subscribeWatcher() (err error) { 263 keyPrefixes := []string{interfaces.StatePrefix} 264 265 p.Log.Debugf("subscribe to %d status prefixes: %v", len(keyPrefixes), keyPrefixes) 266 267 p.watchStatusReg, err = p.Watcher.Watch("vpp-if-state", 268 nil, p.resyncStatusChan, keyPrefixes...) 269 if err != nil { 270 return err 271 } 272 273 return nil 274 } 275 276 // AfterInit delegates the call to ifStateUpdater. 277 func (p *IfPlugin) AfterInit() error { 278 err := p.ifStateUpdater.AfterInit() 279 if err != nil { 280 return err 281 } 282 283 if p.StatusCheck != nil { 284 // Register the plugin to status check. Periodical probe is not needed, 285 // data change will be reported when changed 286 p.StatusCheck.Register(p.PluginName, nil) 287 // Notify that status check for the plugins was registered. It will 288 // prevent status report errors in case resync is executed before AfterInit. 289 p.statusCheckReg = true 290 } 291 292 return nil 293 } 294 295 // Close stops all go routines. 296 func (p *IfPlugin) Close() error { 297 // stop publishing of state data 298 p.cancel() 299 p.wg.Wait() 300 301 // close all resources 302 return safeclose.Close( 303 // DHCP descriptor (DHCP notification watcher) 304 p.dhcpDescriptor, 305 // state updater 306 p.ifStateUpdater, 307 // registrations 308 p.watchStatusReg) 309 } 310 311 // GetInterfaceIndex gives read-only access to map with metadata of all configured 312 // VPP interfaces. 313 func (p *IfPlugin) GetInterfaceIndex() ifaceidx.IfaceMetadataIndex { 314 return p.intfIndex 315 } 316 317 // GetDHCPIndex gives read-only access to (untyped) map with DHCP leases. 318 // Cast metadata to "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces".DHCPLease 319 func (p *IfPlugin) GetDHCPIndex() idxmap.NamedMapping { 320 return p.dhcpIndex 321 } 322 323 // SetNotifyService sets notification callback for processing VPP notifications. 324 func (p *IfPlugin) SetNotifyService(notify func(notification *vpp.Notification)) { 325 p.PushNotification = notify 326 } 327 328 // fromConfigFile loads plugin attributes from the configuration file. 329 func (p *IfPlugin) fromConfigFile() error { 330 config, err := p.loadConfig() 331 if err != nil { 332 p.Log.Errorf("Error reading %v config file: %v", p.PluginName, err) 333 return err 334 } 335 if config != nil { 336 publishers := datasync.KVProtoWriters{} 337 for _, pub := range config.StatusPublishers { 338 db, found := p.Deps.DataSyncs[pub] 339 if !found { 340 p.Log.Warnf("Unknown status publisher %q from config", pub) 341 continue 342 } 343 publishers = append(publishers, db) 344 p.Log.Infof("Added status publisher %q from config", pub) 345 } 346 p.Deps.PublishStatistics = publishers 347 if config.MTU != 0 { 348 p.defaultMtu = config.MTU 349 p.Log.Infof("Default MTU set to %v", p.defaultMtu) 350 } 351 } 352 return nil 353 } 354 355 var ( 356 // noopWriter (no operation writer) helps avoiding NIL pointer based segmentation fault. 357 // It is used as default if some dependency was not injected. 358 noopWriter = datasync.KVProtoWriters{} 359 360 // noopWatcher (no operation watcher) helps avoiding NIL pointer based segmentation fault. 361 // It is used as default if some dependency was not injected. 362 noopWatcher = datasync.KVProtoWatchers{} 363 ) 364 365 // fixNilPointers sets noopWriter & nooWatcher for nil dependencies. 366 func (p *IfPlugin) fixNilPointers() { 367 if p.Deps.PublishErrors == nil { 368 p.Deps.PublishErrors = noopWriter 369 p.Log.Debug("setting default noop writer for PublishErrors dependency") 370 } 371 if p.Deps.PublishStatistics == nil { 372 p.Deps.PublishStatistics = noopWriter 373 p.Log.Debug("setting default noop writer for PublishStatistics dependency") 374 } 375 if p.Deps.NotifyStates == nil { 376 p.Deps.NotifyStates = noopWriter 377 p.Log.Debug("setting default noop writer for NotifyStatistics dependency") 378 } 379 if p.Deps.Watcher == nil { 380 p.Deps.Watcher = noopWatcher 381 p.Log.Debug("setting default noop watcher for Watcher dependency") 382 } 383 }