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  }