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 }