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  }