go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/api/txn_record.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 api 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "strings" 21 "time" 22 23 "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/internal/utils" 24 "go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler" 25 ) 26 27 // TxnType differentiates between NB transaction, retry of failed operations and 28 // SB notification. Once queued, all three different operations are classified 29 // as transactions, only with different parameters. 30 type TxnType int 31 32 const ( 33 // SBNotification is notification from southbound. 34 SBNotification TxnType = iota 35 36 // NBTransaction is transaction from northbound. 37 NBTransaction 38 39 // RetryFailedOps is a transaction re-trying failed operations from previous 40 // northbound transaction. 41 RetryFailedOps 42 ) 43 44 // String returns human-readable string representation of the transaction type. 45 func (t TxnType) String() string { 46 switch t { 47 case SBNotification: 48 return "SBNotification" 49 case NBTransaction: 50 return "NBTransaction" 51 case RetryFailedOps: 52 return "RetryFailedOps" 53 } 54 return "UndefinedTxnType" 55 } 56 57 var txnType_value = map[string]int{ 58 "SBNotification": int(SBNotification), 59 "NBTransaction": int(NBTransaction), 60 "RetryFailedOps": int(RetryFailedOps), 61 } 62 63 func (t TxnType) MarshalJSON() ([]byte, error) { 64 return json.Marshal(t.String()) 65 } 66 67 func (t *TxnType) UnmarshalJSON(b []byte) error { 68 if b[0] == '"' { 69 var s string 70 if err := json.Unmarshal(b, &s); err != nil { 71 return err 72 } 73 if v, ok := txnType_value[s]; ok { 74 *t = TxnType(v) 75 } else { 76 *t = TxnType(-1) 77 } 78 } else { 79 var n int 80 if err := json.Unmarshal(b, &n); err != nil { 81 return err 82 } 83 *t = TxnType(n) 84 } 85 return nil 86 } 87 88 func TxnTypeToString(t TxnType) string { 89 switch t { 90 case NBTransaction: 91 return "NB Transaction" 92 case SBNotification: 93 return "SB Notification" 94 case RetryFailedOps: 95 return "Retry Transaction" 96 } 97 return t.String() 98 } 99 100 func ResyncTypeToString(t ResyncType) string { 101 switch t { 102 case NotResync: 103 return "Not Resync" 104 case FullResync: 105 return "Full Resync" 106 case UpstreamResync: 107 return "NB Sync" 108 case DownstreamResync: 109 return "SB Sync" 110 } 111 return t.String() 112 } 113 114 // RecordedTxn is used to record executed transaction. 115 type RecordedTxn struct { 116 PreRecord bool `json:",omitempty"` // not yet fully recorded, only args + plan + pre-processing errors 117 WithSimulation bool `json:",omitempty"` 118 119 // timestamps 120 Start time.Time 121 Stop time.Time 122 123 // arguments 124 SeqNum uint64 125 TxnType TxnType 126 ResyncType ResyncType `json:",omitempty"` 127 Description string `json:",omitempty"` 128 RetryForTxn uint64 `json:",omitempty"` 129 RetryAttempt int `json:",omitempty"` 130 Values []RecordedKVPair `json:",omitempty"` 131 132 // operations 133 Planned RecordedTxnOps `json:",omitempty"` 134 Executed RecordedTxnOps `json:",omitempty"` 135 } 136 137 // RecordedTxnOp is used to record executed/planned transaction operation. 138 type RecordedTxnOp struct { 139 // identification 140 Operation kvscheduler.TxnOperation 141 Key string 142 143 // changes 144 NewState kvscheduler.ValueState `json:",omitempty"` 145 NewValue *utils.RecordedProtoMessage `json:",omitempty"` 146 NewErr error `json:"-"` 147 NewErrMsg string `json:",omitempty"` 148 PrevState kvscheduler.ValueState `json:",omitempty"` 149 PrevValue *utils.RecordedProtoMessage `json:",omitempty"` 150 PrevErr error `json:"-"` 151 PrevErrMsg string `json:",omitempty"` 152 NOOP bool `json:",omitempty"` 153 154 // flags 155 IsDerived bool `json:",omitempty"` 156 IsProperty bool `json:",omitempty"` 157 IsRevert bool `json:",omitempty"` 158 IsRetry bool `json:",omitempty"` 159 IsRecreate bool `json:",omitempty"` 160 } 161 162 // RecordedKVPair is used to record key-value pair. 163 type RecordedKVPair struct { 164 Key string 165 Value *utils.RecordedProtoMessage 166 Origin ValueOrigin 167 } 168 169 // RecordedTxnOps is a list of recorded executed/planned transaction operations. 170 type RecordedTxnOps []*RecordedTxnOp 171 172 // RecordedTxns is a list of recorded transactions. 173 type RecordedTxns []*RecordedTxn 174 175 // RecordedKVWithMetadata is the same as KVWithMetadata but with the field Value 176 // of type utils.RecordedProtoMessage instead of proto.Message. This allows for 177 // proper JSON marshalling and unmarshalling. Values of this type are used in 178 // KVScheduler's REST API. 179 type RecordedKVWithMetadata struct { 180 RecordedKVPair 181 Metadata Metadata 182 } 183 184 // String returns a *multi-line* human-readable string representation of recorded transaction. 185 func (txn *RecordedTxn) String() string { 186 return txn.StringWithOpts(false, false, 0) 187 } 188 189 // StringWithOpts allows to format string representation of recorded transaction. 190 func (txn *RecordedTxn) StringWithOpts(resultOnly, verbose bool, indent int) string { 191 var str string 192 indent1 := strings.Repeat(" ", indent) 193 indent2 := strings.Repeat(" ", indent+4) 194 indent3 := strings.Repeat(" ", indent+8) 195 196 if !resultOnly { 197 // transaction arguments 198 str += indent1 + "* transaction arguments:\n" 199 str += indent2 + fmt.Sprintf("- seqNum: %d\n", txn.SeqNum) 200 if txn.TxnType == NBTransaction && txn.ResyncType != NotResync { 201 str += indent2 + fmt.Sprintf("- type: %s, %s\n", TxnTypeToString(txn.TxnType), ResyncTypeToString(txn.ResyncType)) 202 } else { 203 if txn.TxnType == RetryFailedOps { 204 str += indent2 + fmt.Sprintf("- type: %s (for txn %d, attempt #%d)\n", 205 TxnTypeToString(txn.TxnType), txn.RetryForTxn, txn.RetryAttempt) 206 } else { 207 str += indent2 + fmt.Sprintf("- type: %s\n", TxnTypeToString(txn.TxnType)) 208 } 209 } 210 if txn.Description != "" { 211 descriptionLines := strings.Split(txn.Description, "\n") 212 for idx, line := range descriptionLines { 213 if idx == 0 { 214 str += indent2 + fmt.Sprintf("- Description: %s\n", line) 215 } else { 216 str += indent3 + fmt.Sprintf("%s\n", line) 217 } 218 } 219 } 220 if txn.ResyncType == DownstreamResync { 221 goto printOps 222 } 223 if len(txn.Values) == 0 { 224 str += indent2 + "- values: NONE\n" 225 } else { 226 str += indent2 + "- values:\n" 227 } 228 for _, kv := range txn.Values { 229 if txn.ResyncType != NotResync && kv.Origin == FromSB { 230 // do not print SB values updated during resync 231 continue 232 } 233 str += indent3 + fmt.Sprintf("- key: %s\n", kv.Key) 234 str += indent3 + fmt.Sprintf(" val: %s\n", utils.ProtoToString(kv.Value)) 235 } 236 237 printOps: 238 // planned operations 239 if txn.WithSimulation { 240 str += indent1 + "* planned operations:\n" 241 str += txn.Planned.StringWithOpts(verbose, indent+4) 242 } 243 } 244 245 if !txn.PreRecord { 246 if len(txn.Executed) == 0 { 247 str += indent1 + "* executed operations:\n" 248 } else { 249 str += indent1 + fmt.Sprintf("* executed operations (%s -> %s, dur: %s):\n", 250 txn.Start.Round(time.Millisecond), 251 txn.Stop.Round(time.Millisecond), 252 txn.Stop.Sub(txn.Start).Round(time.Millisecond)) 253 } 254 str += txn.Executed.StringWithOpts(verbose, indent+4) 255 } 256 257 return str 258 } 259 260 // String returns a *multi-line* human-readable string representation of a recorded 261 // transaction operation. 262 func (op *RecordedTxnOp) String() string { 263 return op.StringWithOpts(0, false, 0) 264 } 265 266 // StringWithOpts allows to format string representation of a transaction operation. 267 func (op *RecordedTxnOp) StringWithOpts(index int, verbose bool, indent int) string { 268 var str string 269 indent1 := strings.Repeat(" ", indent) 270 indent2 := strings.Repeat(" ", indent+4) 271 272 var flags []string 273 // operation flags 274 if op.IsDerived && !op.IsProperty { 275 flags = append(flags, "DERIVED") 276 } 277 if op.IsProperty { 278 flags = append(flags, "PROPERTY") 279 } 280 if op.NOOP { 281 flags = append(flags, "NOOP") 282 } 283 if op.IsRevert && !op.IsProperty { 284 flags = append(flags, "REVERT") 285 } 286 if op.IsRetry && !op.IsProperty { 287 flags = append(flags, "RETRY") 288 } 289 if op.IsRecreate { 290 flags = append(flags, "RECREATE") 291 } 292 // value state transition 293 // -> OBTAINED 294 if op.NewState == kvscheduler.ValueState_OBTAINED { 295 flags = append(flags, "OBTAINED") 296 } 297 if op.PrevState == kvscheduler.ValueState_OBTAINED && op.PrevState != op.NewState { 298 flags = append(flags, "WAS-OBTAINED") 299 } 300 // -> UNIMPLEMENTED 301 if op.NewState == kvscheduler.ValueState_UNIMPLEMENTED { 302 flags = append(flags, "UNIMPLEMENTED") 303 } 304 if op.PrevState == kvscheduler.ValueState_UNIMPLEMENTED && op.PrevState != op.NewState { 305 flags = append(flags, "WAS-UNIMPLEMENTED") 306 } 307 // -> REMOVED / MISSING 308 if op.PrevState == kvscheduler.ValueState_REMOVED && op.Operation == kvscheduler.TxnOperation_DELETE { 309 flags = append(flags, "ALREADY-REMOVED") 310 } 311 if op.PrevState == kvscheduler.ValueState_MISSING { 312 if op.NewState == kvscheduler.ValueState_REMOVED { 313 flags = append(flags, "ALREADY-MISSING") 314 } else { 315 flags = append(flags, "WAS-MISSING") 316 } 317 } 318 // -> DISCOVERED 319 if op.PrevState == kvscheduler.ValueState_DISCOVERED { 320 flags = append(flags, "DISCOVERED") 321 } 322 // -> PENDING 323 if op.PrevState == kvscheduler.ValueState_PENDING { 324 if op.NewState == kvscheduler.ValueState_PENDING { 325 flags = append(flags, "STILL-PENDING") 326 } else { 327 flags = append(flags, "WAS-PENDING") 328 } 329 } else { 330 if op.NewState == kvscheduler.ValueState_PENDING { 331 flags = append(flags, "IS-PENDING") 332 } 333 } 334 // -> FAILED / INVALID 335 if op.PrevState == kvscheduler.ValueState_FAILED { 336 if op.NewState == kvscheduler.ValueState_FAILED { 337 flags = append(flags, "STILL-FAILING") 338 } else if op.NewState == kvscheduler.ValueState_CONFIGURED { 339 flags = append(flags, "FIXED") 340 } 341 } else { 342 if op.NewState == kvscheduler.ValueState_FAILED { 343 flags = append(flags, "FAILED") 344 } 345 } 346 if op.PrevState == kvscheduler.ValueState_INVALID { 347 if op.NewState == kvscheduler.ValueState_INVALID { 348 flags = append(flags, "STILL-INVALID") 349 } else if op.NewState == kvscheduler.ValueState_CONFIGURED { 350 flags = append(flags, "FIXED") 351 } 352 } else { 353 if op.NewState == kvscheduler.ValueState_INVALID { 354 flags = append(flags, "INVALID") 355 } 356 } 357 358 if index > 0 { 359 if len(flags) == 0 { 360 str += indent1 + fmt.Sprintf("%d. %s:\n", index, op.Operation.String()) 361 } else { 362 str += indent1 + fmt.Sprintf("%d. %s %v:\n", index, op.Operation.String(), flags) 363 } 364 } else { 365 if len(flags) == 0 { 366 str += indent1 + fmt.Sprintf("%s:\n", op.Operation.String()) 367 } else { 368 str += indent1 + fmt.Sprintf("%s %v:\n", op.Operation.String(), flags) 369 } 370 } 371 372 str += indent2 + fmt.Sprintf("- key: %s\n", op.Key) 373 if op.Operation == kvscheduler.TxnOperation_UPDATE { 374 str += indent2 + fmt.Sprintf("- prev-value: %s \n", utils.ProtoToString(op.PrevValue)) 375 str += indent2 + fmt.Sprintf("- new-value: %s \n", utils.ProtoToString(op.NewValue)) 376 } 377 if op.Operation == kvscheduler.TxnOperation_DELETE { 378 str += indent2 + fmt.Sprintf("- value: %s \n", utils.ProtoToString(op.PrevValue)) 379 } 380 if op.Operation == kvscheduler.TxnOperation_CREATE { 381 str += indent2 + fmt.Sprintf("- value: %s \n", utils.ProtoToString(op.NewValue)) 382 } 383 if op.PrevErr != nil { 384 str += indent2 + fmt.Sprintf("- prev-error: %s\n", utils.ErrorToString(op.PrevErr)) 385 } 386 if op.NewErr != nil { 387 str += indent2 + fmt.Sprintf("- error: %s\n", utils.ErrorToString(op.NewErr)) 388 } 389 if verbose { 390 str += indent2 + fmt.Sprintf("- prev-state: %s \n", op.PrevState.String()) 391 str += indent2 + fmt.Sprintf("- new-state: %s \n", op.NewState.String()) 392 } 393 394 return str 395 } 396 397 // String returns a *multi-line* human-readable string representation of transaction 398 // operations. 399 func (ops RecordedTxnOps) String() string { 400 return ops.StringWithOpts(false, 0) 401 } 402 403 // StringWithOpts allows to format string representation of transaction operations. 404 func (ops RecordedTxnOps) StringWithOpts(verbose bool, indent int) string { 405 if len(ops) == 0 { 406 return strings.Repeat(" ", indent) + "<NONE>\n" 407 } 408 409 var str string 410 for idx, op := range ops { 411 str += op.StringWithOpts(idx+1, verbose, indent) 412 } 413 return str 414 } 415 416 // String returns a *multi-line* human-readable string representation of a transaction 417 // list. 418 func (txns RecordedTxns) String() string { 419 return txns.StringWithOpts(false, false, 0) 420 } 421 422 // StringWithOpts allows to format string representation of a transaction list. 423 func (txns RecordedTxns) StringWithOpts(resultOnly, verbose bool, indent int) string { 424 if len(txns) == 0 { 425 return strings.Repeat(" ", indent) + "<NONE>\n" 426 } 427 428 var str string 429 for idx, txn := range txns { 430 str += strings.Repeat(" ", indent) + fmt.Sprintf("Transaction #%d:\n", txn.SeqNum) 431 str += txn.StringWithOpts(resultOnly, verbose, indent+4) 432 if idx < len(txns)-1 { 433 str += "\n" 434 } 435 } 436 return str 437 }