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

     1  // Copyright (c) 2019 Bell Canada, Pantheon Technologies 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  	"fmt"
    19  	"net"
    20  	"reflect"
    21  	"strings"
    22  
    23  	"github.com/pkg/errors"
    24  	"go.ligato.io/cn-infra/v2/logging"
    25  
    26  	scheduler "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    27  	"go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/descriptor/adapter"
    28  	"go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls"
    29  	vpp_interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    30  	vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
    31  	srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6"
    32  )
    33  
    34  const (
    35  	// SteeringDescriptorName is the name of the descriptor for VPP SRv6 steering
    36  	SteeringDescriptorName = "vpp-sr-steering"
    37  
    38  	// dependency labels
    39  	policyExistsDep      = "sr-policy-for-steering-exists"
    40  	steeringVRFDep       = "sr-steering-vrf-table-exists"
    41  	steeringInterfaceDep = "sr-steering-l2-interface-exists"
    42  )
    43  
    44  // SteeringDescriptor teaches KVScheduler how to configure VPP SRv6 steering.
    45  type SteeringDescriptor struct {
    46  	// dependencies
    47  	log       logging.Logger
    48  	srHandler vppcalls.SRv6VppAPI
    49  }
    50  
    51  // NewSteeringDescriptor creates a new instance of the Srv6 steering descriptor.
    52  func NewSteeringDescriptor(srHandler vppcalls.SRv6VppAPI, log logging.PluginLogger) *scheduler.KVDescriptor {
    53  	ctx := &SteeringDescriptor{
    54  		log:       log.NewLogger("steering-descriptor"),
    55  		srHandler: srHandler,
    56  	}
    57  
    58  	typedDescr := &adapter.SteeringDescriptor{
    59  		Name:            SteeringDescriptorName,
    60  		NBKeyPrefix:     srv6.ModelSteering.KeyPrefix(),
    61  		ValueTypeName:   srv6.ModelSteering.ProtoName(),
    62  		KeySelector:     srv6.ModelSteering.IsKeyValid,
    63  		KeyLabel:        srv6.ModelSteering.StripKeyPrefix,
    64  		ValueComparator: ctx.EquivalentSteering,
    65  		Validate:        ctx.Validate,
    66  		Create:          ctx.Create,
    67  		Delete:          ctx.Delete,
    68  		Dependencies:    ctx.Dependencies,
    69  	}
    70  	return adapter.NewSteeringDescriptor(typedDescr)
    71  }
    72  
    73  // EquivalentSteering determines whether 2 steerings are logically equal. This comparison takes into consideration also
    74  // semantics that couldn't be modeled into proto models (i.e. SID is IPv6 address and not only string)
    75  func (d *SteeringDescriptor) EquivalentSteering(key string, oldSteering, newSteering *srv6.Steering) bool {
    76  	return strings.TrimSpace(oldSteering.Name) == strings.TrimSpace(newSteering.Name) &&
    77  		d.equivalentPolicy(oldSteering.PolicyRef, newSteering.PolicyRef) &&
    78  		d.equivalentTraffic(oldSteering.Traffic, newSteering.Traffic)
    79  }
    80  
    81  func (d *SteeringDescriptor) equivalentPolicy(policy1, policy2 interface{}) bool {
    82  	if policy1 == nil || policy2 == nil {
    83  		return policy1 == policy2
    84  	}
    85  	if reflect.TypeOf(policy1) != reflect.TypeOf(policy2) {
    86  		return false
    87  	}
    88  	switch policy1typed := policy1.(type) {
    89  	case *srv6.Steering_PolicyBsid:
    90  		return equivalentSIDs(policy1typed.PolicyBsid, policy2.(*srv6.Steering_PolicyBsid).PolicyBsid)
    91  	case *srv6.Steering_PolicyIndex:
    92  		return policy1typed.PolicyIndex == policy2.(*srv6.Steering_PolicyIndex).PolicyIndex
    93  	default:
    94  		d.log.Warn("EquivalentSteering found unknown policy reference type (%T). Using general reflect.DeepEqual for it.", policy1)
    95  		return reflect.DeepEqual(policy1, policy2) // unknown policies
    96  	}
    97  }
    98  
    99  func (d *SteeringDescriptor) equivalentTraffic(traffic1, traffic2 interface{}) bool {
   100  	if traffic1 == nil || traffic2 == nil {
   101  		return traffic1 == traffic2
   102  	}
   103  	if reflect.TypeOf(traffic1) != reflect.TypeOf(traffic2) {
   104  		return false
   105  	}
   106  	switch traffic1typed := traffic1.(type) {
   107  	case *srv6.Steering_L3Traffic_:
   108  		if traffic1typed.L3Traffic.InstallationVrfId != traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.InstallationVrfId {
   109  			return false
   110  		}
   111  		ip1, ipNet1, err1 := net.ParseCIDR(traffic1typed.L3Traffic.PrefixAddress)
   112  		ip2, ipNet2, err2 := net.ParseCIDR(traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.PrefixAddress)
   113  		if err1 != nil || err2 != nil {
   114  			return equivalentTrimmedLowered(traffic1typed.L3Traffic.PrefixAddress, traffic2.(*srv6.Steering_L3Traffic_).L3Traffic.PrefixAddress) // invalid one of prefixes, but still can equal on string basis
   115  		}
   116  		return ip1.Equal(ip2) && ipNet1.IP.Equal(ipNet2.IP) && ipNet1.Mask.String() == ipNet2.Mask.String()
   117  	case *srv6.Steering_L2Traffic_:
   118  		return equivalentTrimmedLowered(traffic1typed.L2Traffic.InterfaceName, traffic2.(*srv6.Steering_L2Traffic_).L2Traffic.InterfaceName)
   119  	default:
   120  		d.log.Warn("EquivalentSteering found unknown traffic type (%T). Using general reflect.DeepEqual for it.", traffic1)
   121  		return reflect.DeepEqual(traffic1, traffic2) // unknown policies
   122  	}
   123  }
   124  
   125  // Validate validates VPP SRv6 steerings.
   126  func (d *SteeringDescriptor) Validate(key string, steering *srv6.Steering) error {
   127  	// checking policy reference ("oneof" field)
   128  	switch ref := steering.PolicyRef.(type) {
   129  	case *srv6.Steering_PolicyBsid:
   130  		_, err := ParseIPv6(ref.PolicyBsid)
   131  		if err != nil {
   132  			return scheduler.NewInvalidValueError(errors.Errorf("failed to parse steering's policy bsid %s, should be a valid ipv6 address: %v", ref.PolicyBsid, err), "policybsid")
   133  		}
   134  	case nil:
   135  		return scheduler.NewInvalidValueError(errors.New("policy reference must be filled, either by policy bsid or policy index"), "PolicyRef(policybsid/policyindex)")
   136  	default:
   137  		return scheduler.NewInvalidValueError(errors.Errorf("policy reference has unexpected model link type %T", ref), "PolicyRef")
   138  	}
   139  
   140  	// checking traffic ("oneof" field)
   141  	switch t := steering.Traffic.(type) {
   142  	case *srv6.Steering_L2Traffic_:
   143  		if strings.TrimSpace(t.L2Traffic.InterfaceName) == "" {
   144  			return scheduler.NewInvalidValueError(errors.New("(non-empty) interface name must be given (L2 traffic definition)"), "InterfaceName")
   145  		}
   146  	case *srv6.Steering_L3Traffic_:
   147  		_, _, err := net.ParseCIDR(t.L3Traffic.PrefixAddress)
   148  		if err != nil {
   149  			return scheduler.NewInvalidValueError(errors.Errorf("steering l3 traffic prefix address %v can't be parsed as CIDR formatted prefix: %v", t.L3Traffic.PrefixAddress, err), "prefixaddress")
   150  		}
   151  	case nil:
   152  		return scheduler.NewInvalidValueError(errors.New("steering must have filled traffic otherwise we don't know which traffic to steer into policy"), "traffic")
   153  	default:
   154  		return scheduler.NewInvalidValueError(errors.Errorf("steering's traffic reference has unexpected model link type %T", t), "traffic")
   155  	}
   156  	return nil
   157  }
   158  
   159  // Create adds new steering into VPP using VPP's binary api
   160  func (d *SteeringDescriptor) Create(key string, steering *srv6.Steering) (metadata interface{}, err error) {
   161  	if err := d.srHandler.AddSteering(steering); err != nil {
   162  		name, valid := srv6.ModelSteering.ParseKey(key)
   163  		if !valid {
   164  			name = fmt.Sprintf("(key %v)", key)
   165  		}
   166  		return nil, errors.Errorf("failed to add steering %s: %v", name, err)
   167  	}
   168  	return nil, nil
   169  }
   170  
   171  // Delete removes steering from VPP using VPP's binary api
   172  func (d *SteeringDescriptor) Delete(key string, steering *srv6.Steering, metadata interface{}) error {
   173  	if err := d.srHandler.RemoveSteering(steering); err != nil {
   174  		name, valid := srv6.ModelSteering.ParseKey(key)
   175  		if !valid {
   176  			name = fmt.Sprintf("(key %v)", key)
   177  		}
   178  		return errors.Errorf("failed to remove steering %s: %v", name, err)
   179  	}
   180  	return nil
   181  }
   182  
   183  // Dependencies defines dependencies of Steering descriptor
   184  func (d *SteeringDescriptor) Dependencies(key string, steering *srv6.Steering) (dependencies []scheduler.Dependency) {
   185  	//TODO support also policy identification by index for Dependency waiting (impl using derived value) and do proper robot test for it (vppcalls and NB support it already)
   186  	if _, ok := steering.GetPolicyRef().(*srv6.Steering_PolicyBsid); !ok {
   187  		d.log.Errorf("Non-BSID policy reference in steering is not fully supported (dependency waiting is not "+
   188  			"implemented). Using empty dependencies for steering %+v", steering)
   189  		return dependencies
   190  	}
   191  
   192  	dependencies = append(dependencies, scheduler.Dependency{
   193  		Label: policyExistsDep,
   194  		Key:   srv6.PolicyKey(steering.GetPolicyBsid()),
   195  	})
   196  
   197  	// VRF-table dependency
   198  	if t, isL3Traffic := steering.Traffic.(*srv6.Steering_L3Traffic_); isL3Traffic {
   199  		l3Traffic := t.L3Traffic
   200  		tableID := l3Traffic.InstallationVrfId
   201  		if tableID > 0 {
   202  			ip, _, _ := net.ParseCIDR(l3Traffic.PrefixAddress) // PrefixAddress is already validated
   203  			tableProto := vpp_l3.VrfTable_IPV6
   204  			if ip.To4() != nil { // IPv4 address
   205  				tableProto = vpp_l3.VrfTable_IPV4
   206  			}
   207  			dependencies = append(dependencies, scheduler.Dependency{
   208  				Label: steeringVRFDep,
   209  				Key:   vpp_l3.VrfTableKey(tableID, tableProto),
   210  			})
   211  		}
   212  	}
   213  	// Interface dependency
   214  	if l2Traffic := steering.GetL2Traffic(); l2Traffic != nil {
   215  		interfaceName := strings.TrimSpace(l2Traffic.InterfaceName)
   216  		if len(interfaceName) > 0 {
   217  			dependencies = append(dependencies, scheduler.Dependency{
   218  				Label: steeringInterfaceDep,
   219  				Key:   vpp_interfaces.InterfaceKey(interfaceName),
   220  			})
   221  		}
   222  	}
   223  	return dependencies
   224  }