go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/internal/test/southbound.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 test
    16  
    17  import (
    18  	"sync"
    19  
    20  	"google.golang.org/protobuf/proto"
    21  
    22  	. "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    23  )
    24  
    25  // MockSouthbound is used in UTs to simulate the state of the southbound for the scheduler.
    26  type MockSouthbound struct {
    27  	sync.Mutex
    28  
    29  	values         map[string]*KVWithMetadata // key -> value
    30  	plannedErrors  map[string][]plannedError  // key -> planned error
    31  	derivedKeys    map[string]struct{}
    32  	opHistory      []MockOperation // from the oldest to the latest
    33  	invalidKeyData map[string]struct{}
    34  }
    35  
    36  // MockOpType is used to remember the type of a simulated operation.
    37  type MockOpType int
    38  
    39  const (
    40  	// MockCreate is a mock Create operation.
    41  	MockCreate MockOpType = iota
    42  	// MockUpdate is a mock Update operation.
    43  	MockUpdate
    44  	// MockDelete is a mock Delete operation.
    45  	MockDelete
    46  	// MockRetrieve is a mock Retrieve operation.
    47  	MockRetrieve
    48  )
    49  
    50  // MockOperation is used in UTs to remember executed descriptor operations.
    51  type MockOperation struct {
    52  	OpType            MockOpType
    53  	Descriptor        string
    54  	Key               string
    55  	Value             proto.Message
    56  	Err               error
    57  	CorrelateRetrieve []KVWithMetadata
    58  }
    59  
    60  // plannedError is used to simulate error situation.
    61  type plannedError struct {
    62  	err         error
    63  	afterErrClb func() // update values after error via SetValue()
    64  }
    65  
    66  // NewMockSouthbound creates a new instance of SB mock.
    67  func NewMockSouthbound() *MockSouthbound {
    68  	return &MockSouthbound{
    69  		values:         make(map[string]*KVWithMetadata),
    70  		plannedErrors:  make(map[string][]plannedError),
    71  		derivedKeys:    make(map[string]struct{}),
    72  		invalidKeyData: make(map[string]struct{}),
    73  	}
    74  }
    75  
    76  // GetKeysWithInvalidData returns a set of keys for which invalid data were provided
    77  // in one of the descriptor's operations.
    78  func (ms *MockSouthbound) GetKeysWithInvalidData() map[string]struct{} {
    79  	return ms.invalidKeyData
    80  }
    81  
    82  // PlanError is used to simulate error situation for the next operation over the given key.
    83  func (ms *MockSouthbound) PlanError(key string, err error, afterErrClb func()) {
    84  	ms.Lock()
    85  	defer ms.Unlock()
    86  
    87  	if _, has := ms.plannedErrors[key]; !has {
    88  		ms.plannedErrors[key] = []plannedError{}
    89  	}
    90  	ms.plannedErrors[key] = append(ms.plannedErrors[key], plannedError{err: err, afterErrClb: afterErrClb})
    91  }
    92  
    93  // SetValue is used in UTs to prepare the state of SB for the next Retrieve.
    94  func (ms *MockSouthbound) SetValue(key string, value proto.Message, metadata Metadata, origin ValueOrigin, isDerived bool) {
    95  	ms.Lock()
    96  	defer ms.Unlock()
    97  
    98  	ms.setValueUnsafe(key, value, metadata, origin, isDerived)
    99  }
   100  
   101  // GetValue can be used in UTs to query the state of simulated SB.
   102  func (ms *MockSouthbound) GetValue(key string) *KVWithMetadata {
   103  	ms.Lock()
   104  	defer ms.Unlock()
   105  
   106  	if _, hasValue := ms.values[key]; !hasValue {
   107  		return nil
   108  	}
   109  	return ms.values[key]
   110  }
   111  
   112  // GetValues can be used in UTs to query the state of simulated SB.
   113  func (ms *MockSouthbound) GetValues(selector KeySelector) []*KVWithMetadata {
   114  	ms.Lock()
   115  	defer ms.Unlock()
   116  
   117  	var values []*KVWithMetadata
   118  	for _, kv := range ms.values {
   119  		if selector != nil && !selector(kv.Key) {
   120  			continue
   121  		}
   122  		values = append(values, kv)
   123  	}
   124  
   125  	return values
   126  }
   127  
   128  // PopHistoryOfOps returns and simultaneously clears the history of executed descriptor operations.
   129  func (ms *MockSouthbound) PopHistoryOfOps() []MockOperation {
   130  	ms.Lock()
   131  	defer ms.Unlock()
   132  
   133  	history := ms.opHistory
   134  	ms.opHistory = []MockOperation{}
   135  	return history
   136  }
   137  
   138  // setValueUnsafe changes the value under given key without acquiring the lock.
   139  func (ms *MockSouthbound) setValueUnsafe(key string, value proto.Message, metadata Metadata, origin ValueOrigin, isDerived bool) {
   140  	if value == nil {
   141  		delete(ms.values, key)
   142  	} else {
   143  		ms.values[key] = &KVWithMetadata{Key: key, Value: value, Metadata: metadata, Origin: origin}
   144  	}
   145  	if isDerived {
   146  		ms.derivedKeys[key] = struct{}{}
   147  	}
   148  }
   149  
   150  // registerDerivedKey is used to remember that the given key points to a derived value.
   151  // Used by MockDescriptor.
   152  func (ms *MockSouthbound) registerDerivedKey(key string) {
   153  	ms.Lock()
   154  	defer ms.Unlock()
   155  	ms.derivedKeys[key] = struct{}{}
   156  }
   157  
   158  // isKeyDerived returns true if the given key belongs to a derived value.
   159  func (ms *MockSouthbound) isKeyDerived(key string) bool {
   160  	_, isDerived := ms.derivedKeys[key]
   161  	return isDerived
   162  }
   163  
   164  // registerKeyWithInvalidData is used to remember that for the given key invalid input
   165  // data were provided.
   166  func (ms *MockSouthbound) registerKeyWithInvalidData(key string) {
   167  	//panic(key)
   168  	ms.invalidKeyData[key] = struct{}{}
   169  }
   170  
   171  // retrieve returns non-derived values under the given selector.
   172  // Used by MockDescriptor.
   173  func (ms *MockSouthbound) retrieve(descriptor string, correlate []KVWithMetadata, selector KeySelector) ([]KVWithMetadata, error) {
   174  	ms.Lock()
   175  	defer ms.Unlock()
   176  
   177  	var values []KVWithMetadata
   178  	for _, kv := range ms.values {
   179  		if ms.isKeyDerived(kv.Key) || !selector(kv.Key) {
   180  			continue
   181  		}
   182  		values = append(values, KVWithMetadata{
   183  			Key:      kv.Key,
   184  			Value:    kv.Value,
   185  			Metadata: kv.Metadata,
   186  			Origin:   kv.Origin,
   187  		})
   188  	}
   189  
   190  	ms.opHistory = append(ms.opHistory, MockOperation{
   191  		OpType:            MockRetrieve,
   192  		Descriptor:        descriptor,
   193  		CorrelateRetrieve: correlate,
   194  	})
   195  	return values, nil
   196  }
   197  
   198  // executeChange is used by MockDescriptor to simulate execution of a operation in SB.
   199  func (ms *MockSouthbound) executeChange(descriptor string, opType MockOpType, key string, value proto.Message, metadata Metadata) error {
   200  	ms.Lock()
   201  
   202  	operation := MockOperation{OpType: opType, Descriptor: descriptor, Key: key, Value: value}
   203  
   204  	plannedErrors, hasErrors := ms.plannedErrors[key]
   205  	if hasErrors {
   206  		// simulate error situation
   207  		ms.plannedErrors[key] = plannedErrors[1:]
   208  		if len(ms.plannedErrors[key]) == 0 {
   209  			delete(ms.plannedErrors, key)
   210  		}
   211  		err := plannedErrors[0].err
   212  		clb := plannedErrors[0].afterErrClb
   213  		if err != nil {
   214  			operation.Err = err
   215  			ms.opHistory = append(ms.opHistory, operation)
   216  			ms.Unlock()
   217  
   218  			if clb != nil {
   219  				clb()
   220  			}
   221  
   222  			return err
   223  		}
   224  	}
   225  
   226  	// the simulated operation has succeeded
   227  	ms.setValueUnsafe(key, value, metadata, FromNB, ms.isKeyDerived(key))
   228  	ms.opHistory = append(ms.opHistory, operation)
   229  	ms.Unlock()
   230  	return nil
   231  }