go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/puntplugin/descriptor/punt_to_host.go (about)

     1  // Copyright (c) 2018 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 descriptor
    16  
    17  import (
    18  	"errors"
    19  	"strings"
    20  
    21  	"go.ligato.io/cn-infra/v2/logging"
    22  	"google.golang.org/protobuf/proto"
    23  
    24  	"go.ligato.io/vpp-agent/v3/pkg/models"
    25  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    26  	"go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/descriptor/adapter"
    27  	"go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls"
    28  	punt "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/punt"
    29  )
    30  
    31  const (
    32  	// PuntToHostDescriptorName is the name of the descriptor for the VPP punt to host/socket
    33  	PuntToHostDescriptorName = "vpp-punt-to-host"
    34  )
    35  
    36  // A list of non-retriable errors:
    37  var (
    38  	// ErrPuntWithoutL3Protocol is returned when VPP punt has undefined L3 protocol.
    39  	ErrPuntWithoutL3Protocol = errors.New("VPP punt defined without L3 protocol")
    40  
    41  	// ErrPuntWithoutL4Protocol is returned when VPP punt has undefined L4 protocol.
    42  	ErrPuntWithoutL4Protocol = errors.New("VPP punt defined without L4 protocol")
    43  
    44  	// ErrPuntWithoutPort is returned when VPP punt has undefined port.
    45  	ErrPuntWithoutPort = errors.New("VPP punt defined without port")
    46  
    47  	// ErrPuntWithoutSocketPath is returned when VPP punt has undefined socket path.
    48  	ErrPuntWithoutSocketPath = errors.New("VPP punt defined without socket path")
    49  )
    50  
    51  // PuntToHostDescriptor teaches KVScheduler how to configure VPP punt to host or unix domain socket.
    52  type PuntToHostDescriptor struct {
    53  	RegisterSocketFn func(register bool, toHost *punt.ToHost, socketPath string)
    54  
    55  	// dependencies
    56  	log         logging.Logger
    57  	puntHandler vppcalls.PuntVppAPI
    58  }
    59  
    60  // NewPuntToHostDescriptor creates a new instance of the punt to host descriptor.
    61  func NewPuntToHostDescriptor(puntHandler vppcalls.PuntVppAPI, log logging.LoggerFactory) *PuntToHostDescriptor {
    62  	return &PuntToHostDescriptor{
    63  		log:         log.NewLogger("punt-to-host-descriptor"),
    64  		puntHandler: puntHandler,
    65  	}
    66  }
    67  
    68  // GetDescriptor returns descriptor suitable for registration (via adapter) with
    69  // the KVScheduler.
    70  func (d *PuntToHostDescriptor) GetDescriptor() *adapter.PuntToHostDescriptor {
    71  	return &adapter.PuntToHostDescriptor{
    72  		Name:            PuntToHostDescriptorName,
    73  		NBKeyPrefix:     punt.ModelToHost.KeyPrefix(),
    74  		ValueTypeName:   punt.ModelToHost.ProtoName(),
    75  		KeySelector:     punt.ModelToHost.IsKeyValid,
    76  		KeyLabel:        punt.ModelToHost.StripKeyPrefix,
    77  		ValueComparator: d.EquivalentPuntToHost,
    78  		Validate:        d.Validate,
    79  		Create:          d.Create,
    80  		Delete:          d.Delete,
    81  		Retrieve:        d.Retrieve,
    82  	}
    83  }
    84  
    85  // EquivalentPuntToHost is case-insensitive comparison function for punt.ToHost.
    86  func (d *PuntToHostDescriptor) EquivalentPuntToHost(key string, oldPunt, newPunt *punt.ToHost) bool {
    87  	if oldPunt.L3Protocol != newPunt.L3Protocol ||
    88  		oldPunt.L4Protocol != newPunt.L4Protocol ||
    89  		oldPunt.Port != newPunt.Port {
    90  		return false
    91  	}
    92  
    93  	// if the socket path contains '!' as prefix we return false
    94  	// to force scheduler to recreate (register) punt socket
    95  	if strings.HasPrefix(oldPunt.SocketPath, "!") {
    96  		return false
    97  	}
    98  
    99  	return true
   100  }
   101  
   102  // Validate validates VPP punt configuration.
   103  func (d *PuntToHostDescriptor) Validate(key string, puntCfg *punt.ToHost) error {
   104  	// validate L3 protocol
   105  	switch puntCfg.L3Protocol {
   106  	case punt.L3Protocol_IPV4:
   107  	case punt.L3Protocol_IPV6:
   108  	case punt.L3Protocol_ALL:
   109  	default:
   110  		return kvs.NewInvalidValueError(ErrPuntWithoutL3Protocol, "l3_protocol")
   111  	}
   112  
   113  	// validate L4 protocol
   114  	switch puntCfg.L4Protocol {
   115  	case punt.L4Protocol_TCP:
   116  	case punt.L4Protocol_UDP:
   117  	default:
   118  		return kvs.NewInvalidValueError(ErrPuntWithoutL4Protocol, "l4_protocol")
   119  	}
   120  
   121  	if puntCfg.Port == 0 {
   122  		return kvs.NewInvalidValueError(ErrPuntWithoutPort, "port")
   123  	}
   124  
   125  	// TODO: maybe this should also have dependency on socket file existing??
   126  	if puntCfg.SocketPath == "" {
   127  		return kvs.NewInvalidValueError(ErrPuntWithoutSocketPath, "socket_path")
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // Create adds new punt to host entry or registers new punt to unix domain socket.
   134  func (d *PuntToHostDescriptor) Create(key string, punt *punt.ToHost) (interface{}, error) {
   135  	// register punt to socket
   136  	pathname, err := d.puntHandler.RegisterPuntSocket(punt)
   137  	if err != nil {
   138  		d.log.Error(err)
   139  		return nil, err
   140  	}
   141  
   142  	if d.RegisterSocketFn != nil {
   143  		d.RegisterSocketFn(true, punt, pathname)
   144  	}
   145  
   146  	return nil, nil
   147  }
   148  
   149  // Delete removes VPP punt configuration.
   150  func (d *PuntToHostDescriptor) Delete(key string, p *punt.ToHost, metadata interface{}) error {
   151  	// check if the socketpath contains '!' as prefix from retrieve
   152  	punt := proto.Clone(p).(*punt.ToHost)
   153  	punt.SocketPath = strings.TrimPrefix(punt.SocketPath, "!")
   154  
   155  	// deregister punt to socket
   156  	if err := d.puntHandler.DeregisterPuntSocket(punt); err != nil {
   157  		d.log.Error(err)
   158  		return err
   159  	}
   160  
   161  	if d.RegisterSocketFn != nil {
   162  		d.RegisterSocketFn(false, punt, "")
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  // Retrieve returns all configured VPP punt to host entries.
   169  func (d *PuntToHostDescriptor) Retrieve(correlate []adapter.PuntToHostKVWithMetadata) (retrieved []adapter.PuntToHostKVWithMetadata, err error) {
   170  	// Dump registered punt sockets
   171  	socks, err := d.puntHandler.DumpRegisteredPuntSockets()
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	// for all dumped punts that were not yet registered and for which
   177  	// the VPP socket is unknown we prepend '!' as prefix
   178  	// to allow descriptor to recognize this in equivalent
   179  	// and force recreation or make it possible to delete it
   180  	for _, s := range socks {
   181  		if s.PuntData.SocketPath == "" && s.SocketPath != "" {
   182  			s.PuntData.SocketPath = "!" + s.SocketPath
   183  		}
   184  	}
   185  
   186  	// 1. Find NB equivalent of the punt entry with L3 set to 'ALL'. If found, cache
   187  	// the VPP entry. If not found, add to retrieved values.
   188  	var cachedIpv4, cachedIpv6 []*vppcalls.PuntDetails
   189  Retrieved:
   190  	for _, fromVPP := range socks {
   191  		for _, fromNB := range correlate {
   192  			if fromNB.Value.L3Protocol != punt.L3Protocol_ALL {
   193  				continue
   194  			}
   195  			if fromVPP.PuntData.Port == fromNB.Value.Port &&
   196  				fromVPP.PuntData.L4Protocol == fromNB.Value.L4Protocol {
   197  				if fromVPP.PuntData.L3Protocol == punt.L3Protocol_IPV4 {
   198  					cachedIpv4 = append(cachedIpv4, fromVPP)
   199  				}
   200  				if fromVPP.PuntData.L3Protocol == punt.L3Protocol_IPV6 {
   201  					cachedIpv6 = append(cachedIpv6, fromVPP)
   202  				}
   203  				continue Retrieved
   204  			}
   205  		}
   206  		retrieved = append(retrieved, adapter.PuntToHostKVWithMetadata{
   207  			Key:    models.Key(fromVPP.PuntData),
   208  			Value:  fromVPP.PuntData,
   209  			Origin: kvs.FromNB,
   210  		})
   211  	}
   212  
   213  	// 2. Find pairs of the same config.
   214  	//
   215  	// Note: only if both, IPv4 and IPv6 exists the entry is added. Cached IPv4
   216  	// without IPv6 (and all remaining IPv6) are ignored, causing agent to configure
   217  	// the missing one and re-configure the existing one.
   218  	for _, cachedIPv4Punt := range cachedIpv4 {
   219  		// look for IPv6 counterpart
   220  		var found bool
   221  		for _, cachedIPv6Punt := range cachedIpv6 {
   222  			if cachedIPv4Punt.PuntData.L4Protocol == cachedIPv6Punt.PuntData.L4Protocol &&
   223  				cachedIPv4Punt.PuntData.Port == cachedIPv6Punt.PuntData.Port {
   224  				found = true
   225  			}
   226  		}
   227  		// Store as 'ALL entry'
   228  		if found {
   229  			cachedIPv4Punt.PuntData.L3Protocol = punt.L3Protocol_ALL
   230  			retrieved = append(retrieved, adapter.PuntToHostKVWithMetadata{
   231  				Key:    models.Key(cachedIPv4Punt.PuntData),
   232  				Value:  cachedIPv4Punt.PuntData,
   233  				Origin: kvs.FromNB,
   234  			})
   235  		}
   236  	}
   237  
   238  	return retrieved, nil
   239  }