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  }