go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/validation.go (about)

     1  // Copyright (c) 2020 Pantheon.tech
     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 kvscheduler
    16  
    17  import (
    18  	"github.com/go-errors/errors"
    19  	"google.golang.org/protobuf/proto"
    20  	"google.golang.org/protobuf/types/dynamicpb"
    21  
    22  	"go.ligato.io/vpp-agent/v3/pkg/models"
    23  	"go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    24  )
    25  
    26  // ValidateSemantically validates given proto messages according to semantic validation(KVDescriptor.Validate)
    27  // from registered KVDescriptors. If all locally known messages are valid, nil is returned. If some locally known
    28  // messages are invalid, kvscheduler.MessageValidationErrors is returned. In any other case, error is returned.
    29  //
    30  // Usage of dynamic proto messages (dynamicpb.Message) described by remotely known models is not supported.
    31  // The reason for this is that the KVDescriptors can validate only statically generated proto messages and
    32  // remotely retrieved dynamic proto messages can't be converted to such proto messages (there are
    33  // no locally available statically generated proto models).
    34  func (s *Scheduler) ValidateSemantically(messages []proto.Message) error {
    35  	s.txnLock.Lock()
    36  	defer s.txnLock.Unlock()
    37  
    38  	invalidMessageErrors := make([]*api.InvalidMessageError, 0)
    39  	for _, message := range messages {
    40  		originalMessage := message
    41  
    42  		// if needed, convert dynamic proto message to statically generated proto message
    43  		// (validators in descriptors can validate only statically generated proto messages)
    44  		if dynamicMessage, isDyn := message.(*dynamicpb.Message); isDyn {
    45  			model, err := models.GetModelFor(message)
    46  			if err != nil {
    47  				return errors.Errorf("can't get model for message due to: %v (message=%v)", err, message)
    48  			}
    49  			goType := model.LocalGoType() // only for locally known models will return meaningful go type
    50  			if goType == nil {
    51  				s.Log.Debug("instance of %s doesn't have local go type and will be skipped from validation "+
    52  					"using KVDescriptor.Validate  (dynamic message=%v)", model.ProtoName(), message)
    53  				continue
    54  			}
    55  			message, err = models.DynamicLocallyKnownMessageToGeneratedMessage(dynamicMessage)
    56  			if err != nil {
    57  				return errors.Errorf("can't convert dynamic message to statically generated message "+
    58  					"due to: %v (dynamic message=%v)", err, dynamicMessage)
    59  			}
    60  		}
    61  
    62  		// get descriptor and key for given message
    63  		key, err := models.GetKey(message)
    64  		if err != nil {
    65  			return errors.Errorf("can't get message key due to: %v (message=%v)", err, message)
    66  		}
    67  		descriptor := s.registry.GetDescriptorForKey(key)
    68  		if descriptor == nil {
    69  			s.Log.Debug("Skipping validation for proto message key %s "+
    70  				"due to missing descriptor (proto message: %v)", key, message)
    71  			continue
    72  		}
    73  		descHandler := newDescriptorHandler(descriptor)
    74  
    75  		// validate and collect validation errors
    76  		if err = descHandler.validate(key, message); err != nil {
    77  			if ivError, ok := err.(*api.InvalidValueError); ok {
    78  				// only InvalidValueErrors are supposed to describe data invalidity
    79  				invalidMessageErrors = append(invalidMessageErrors,
    80  					api.NewInvalidMessageError(originalMessage, ivError, nil))
    81  			} else {
    82  				s.Log.Warn("Validation of KVDescriptor %s returns error that is not InvalidValueError. "+
    83  					"Using conversion to InvalidValueError, but the invalid fields will be unknown.", descriptor.Name)
    84  				invalidMessageErrors = append(invalidMessageErrors, api.NewInvalidMessageError(
    85  					originalMessage, api.NewInvalidValueError(err, ""), nil))
    86  			}
    87  		}
    88  
    89  		// validate also derived values
    90  		for _, derivedValue := range descHandler.derivedValues(key, message) {
    91  			descriptor = s.registry.GetDescriptorForKey(derivedValue.Key)
    92  			if descriptor == nil {
    93  				s.Log.Debug("Skipping validation for proto message's derived value key %s "+
    94  					"due to missing descriptor (proto message: %v, derived value proto message: %v)",
    95  					derivedValue.Key, message, derivedValue.Value)
    96  				continue
    97  			}
    98  			descHandler = newDescriptorHandler(descriptor)
    99  			if err = descHandler.validate(derivedValue.Key, derivedValue.Value); err != nil {
   100  				if ivError, ok := err.(*api.InvalidValueError); ok {
   101  					// only InvalidValueErrors are supposed to describe data invalidity
   102  					invalidMessageErrors = append(invalidMessageErrors,
   103  						api.NewInvalidMessageError(derivedValue.Value, ivError, originalMessage))
   104  				} else {
   105  					s.Log.Warn("Validation of KVDescriptor %s (derived value validation) "+
   106  						"returns error that is not InvalidValueError. Using conversion "+
   107  						"to InvalidValueError, but the invalid fields will be unknown.", descriptor.Name)
   108  					invalidMessageErrors = append(invalidMessageErrors, api.NewInvalidMessageError(
   109  						derivedValue.Value, api.NewInvalidValueError(err, ""), originalMessage))
   110  				}
   111  			}
   112  		}
   113  	}
   114  
   115  	if len(invalidMessageErrors) > 0 {
   116  		return api.NewInvalidMessagesError(invalidMessageErrors)
   117  	}
   118  	return nil
   119  }