go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/datachange_test.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 kvscheduler
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"google.golang.org/protobuf/proto"
    25  
    26  	. "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    27  	"go.ligato.io/vpp-agent/v3/plugins/kvscheduler/internal/test"
    28  	"go.ligato.io/vpp-agent/v3/plugins/kvscheduler/internal/utils"
    29  	. "go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
    30  )
    31  
    32  var testCtx = WithSimulation(context.Background())
    33  
    34  func TestDataChangeTransactions(t *testing.T) {
    35  	RegisterTestingT(t)
    36  
    37  	// prepare KV Scheduler
    38  	scheduler := NewPlugin(UseDeps(func(deps *Deps) {
    39  		deps.HTTPHandlers = nil
    40  	}))
    41  	err := scheduler.Init()
    42  	Expect(err).To(BeNil())
    43  
    44  	// prepare mocks
    45  	mockSB := test.NewMockSouthbound()
    46  	// -> descriptor1:
    47  	descriptor1 := test.NewMockDescriptor(&KVDescriptor{
    48  		Name:          descriptor1Name,
    49  		NBKeyPrefix:   prefixA,
    50  		KeySelector:   prefixSelector(prefixA),
    51  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
    52  		DerivedValues: test.ArrayValueDerBuilder,
    53  		WithMetadata:  true,
    54  	}, mockSB, 0)
    55  	// -> descriptor2:
    56  	descriptor2 := test.NewMockDescriptor(&KVDescriptor{
    57  		Name:          descriptor2Name,
    58  		NBKeyPrefix:   prefixB,
    59  		KeySelector:   prefixSelector(prefixB),
    60  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
    61  		DerivedValues: test.ArrayValueDerBuilder,
    62  		Dependencies: func(key string, value proto.Message) []Dependency {
    63  			if key == prefixB+baseValue2+"/item1" {
    64  				depKey := prefixA + baseValue1
    65  				return []Dependency{
    66  					{Label: depKey, Key: depKey},
    67  				}
    68  			}
    69  			if key == prefixB+baseValue2+"/item2" {
    70  				depKey := prefixA + baseValue1 + "/item1"
    71  				return []Dependency{
    72  					{Label: depKey, Key: depKey},
    73  				}
    74  			}
    75  			return nil
    76  		},
    77  		WithMetadata:         true,
    78  		RetrieveDependencies: []string{descriptor1Name},
    79  	}, mockSB, 0)
    80  	// -> descriptor3:
    81  	descriptor3 := test.NewMockDescriptor(&KVDescriptor{
    82  		Name:          descriptor3Name,
    83  		NBKeyPrefix:   prefixC,
    84  		KeySelector:   prefixSelector(prefixC),
    85  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
    86  		DerivedValues: test.ArrayValueDerBuilder,
    87  		UpdateWithRecreate: func(key string, oldValue, newValue proto.Message, metadata Metadata) bool {
    88  			return key == prefixC+baseValue3
    89  		},
    90  		WithMetadata:         true,
    91  		RetrieveDependencies: []string{descriptor2Name},
    92  	}, mockSB, 0)
    93  
    94  	// register all 3 descriptors with the scheduler
    95  	scheduler.RegisterKVDescriptor(descriptor1)
    96  	scheduler.RegisterKVDescriptor(descriptor2)
    97  	scheduler.RegisterKVDescriptor(descriptor3)
    98  
    99  	// get metadata map created for each descriptor
   100  	metadataMap := scheduler.GetMetadataMap(descriptor1.Name)
   101  	nameToInteger1, withMetadataMap := metadataMap.(test.NameToInteger)
   102  	Expect(withMetadataMap).To(BeTrue())
   103  	metadataMap = scheduler.GetMetadataMap(descriptor2.Name)
   104  	nameToInteger2, withMetadataMap := metadataMap.(test.NameToInteger)
   105  	Expect(withMetadataMap).To(BeTrue())
   106  	metadataMap = scheduler.GetMetadataMap(descriptor3.Name)
   107  	nameToInteger3, withMetadataMap := metadataMap.(test.NameToInteger)
   108  	Expect(withMetadataMap).To(BeTrue())
   109  
   110  	// run non-resync transaction against empty SB
   111  	startTime := time.Now()
   112  	schedulerTxn := scheduler.StartNBTransaction()
   113  	schedulerTxn.SetValue(prefixB+baseValue2, test.NewArrayValue("item1", "item2"))
   114  	schedulerTxn.SetValue(prefixA+baseValue1, test.NewArrayValue("item2"))
   115  	schedulerTxn.SetValue(prefixC+baseValue3, test.NewArrayValue("item1", "item2"))
   116  	description := "testing data change"
   117  	seqNum, err := schedulerTxn.Commit(WithDescription(testCtx, description))
   118  	stopTime := time.Now()
   119  	Expect(seqNum).To(BeEquivalentTo(0))
   120  	Expect(err).ShouldNot(HaveOccurred())
   121  
   122  	// check the state of SB
   123  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
   124  	// -> base value 1
   125  	value := mockSB.GetValue(prefixA + baseValue1)
   126  	Expect(value).ToNot(BeNil())
   127  	Expect(proto.Equal(value.Value, test.NewArrayValue("item2"))).To(BeTrue())
   128  	Expect(value.Metadata).ToNot(BeNil())
   129  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   130  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   131  	// -> item1 derived from base value was not added
   132  	value = mockSB.GetValue(prefixA + baseValue1 + "/item1")
   133  	Expect(value).To(BeNil())
   134  	// -> item2 derived from base value 1
   135  	value = mockSB.GetValue(prefixA + baseValue1 + "/item2")
   136  	Expect(value).ToNot(BeNil())
   137  	Expect(proto.Equal(value.Value, test.NewStringValue("item2"))).To(BeTrue())
   138  	Expect(value.Metadata).To(BeNil())
   139  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   140  	// -> base value 2
   141  	value = mockSB.GetValue(prefixB + baseValue2)
   142  	Expect(value).ToNot(BeNil())
   143  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1", "item2"))).To(BeTrue())
   144  	Expect(value.Metadata).ToNot(BeNil())
   145  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   146  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   147  	// -> item1 derived from base value 2
   148  	value = mockSB.GetValue(prefixB + baseValue2 + "/item1")
   149  	Expect(value).ToNot(BeNil())
   150  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   151  	Expect(value.Metadata).To(BeNil())
   152  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   153  	// -> item2 derived from base value 2 is pending
   154  	value = mockSB.GetValue(prefixB + baseValue2 + "/item2")
   155  	Expect(value).To(BeNil())
   156  	// -> base value 3
   157  	value = mockSB.GetValue(prefixC + baseValue3)
   158  	Expect(value).ToNot(BeNil())
   159  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1", "item2"))).To(BeTrue())
   160  	Expect(value.Metadata).ToNot(BeNil())
   161  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   162  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   163  	// -> item1 derived from base value 3
   164  	value = mockSB.GetValue(prefixC + baseValue3 + "/item1")
   165  	Expect(value).ToNot(BeNil())
   166  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   167  	Expect(value.Metadata).To(BeNil())
   168  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   169  	// -> item2 derived from base value 3
   170  	value = mockSB.GetValue(prefixC + baseValue3 + "/item2")
   171  	Expect(value).ToNot(BeNil())
   172  	Expect(proto.Equal(value.Value, test.NewStringValue("item2"))).To(BeTrue())
   173  	Expect(value.Metadata).To(BeNil())
   174  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   175  	Expect(mockSB.GetValues(nil)).To(HaveLen(7))
   176  
   177  	// check metadata
   178  	metadata, exists := nameToInteger1.LookupByName(baseValue1)
   179  	Expect(exists).To(BeTrue())
   180  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   181  	metadata, exists = nameToInteger2.LookupByName(baseValue2)
   182  	Expect(exists).To(BeTrue())
   183  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   184  	metadata, exists = nameToInteger3.LookupByName(baseValue3)
   185  	Expect(exists).To(BeTrue())
   186  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   187  
   188  	// check operations executed in SB
   189  	opHistory := mockSB.PopHistoryOfOps()
   190  	Expect(opHistory).To(HaveLen(7))
   191  	operation := opHistory[0]
   192  	Expect(operation.OpType).To(Equal(test.MockCreate))
   193  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   194  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
   195  	Expect(operation.Err).To(BeNil())
   196  	operation = opHistory[1]
   197  	Expect(operation.OpType).To(Equal(test.MockCreate))
   198  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   199  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item2"))
   200  	Expect(operation.Err).To(BeNil())
   201  	operation = opHistory[2]
   202  	Expect(operation.OpType).To(Equal(test.MockCreate))
   203  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor2Name))
   204  	Expect(operation.Key).To(BeEquivalentTo(prefixB + baseValue2))
   205  	Expect(operation.Err).To(BeNil())
   206  	operation = opHistory[3]
   207  	Expect(operation.OpType).To(Equal(test.MockCreate))
   208  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor2Name))
   209  	Expect(operation.Key).To(BeEquivalentTo(prefixB + baseValue2 + "/item1"))
   210  	Expect(operation.Err).To(BeNil())
   211  	operation = opHistory[4]
   212  	Expect(operation.OpType).To(Equal(test.MockCreate))
   213  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   214  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
   215  	Expect(operation.Err).To(BeNil())
   216  	operation = opHistory[5]
   217  	Expect(operation.OpType).To(Equal(test.MockCreate))
   218  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   219  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
   220  	Expect(operation.Err).To(BeNil())
   221  	operation = opHistory[6]
   222  	Expect(operation.OpType).To(Equal(test.MockCreate))
   223  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   224  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item2"))
   225  	Expect(operation.Err).To(BeNil())
   226  
   227  	// check transaction operations
   228  	txnHistory := scheduler.GetTransactionHistory(time.Time{}, time.Now())
   229  	Expect(txnHistory).To(HaveLen(1))
   230  	txn := txnHistory[0]
   231  	Expect(txn.PreRecord).To(BeFalse())
   232  	Expect(txn.Start.After(startTime)).To(BeTrue())
   233  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
   234  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
   235  	Expect(txn.SeqNum).To(BeEquivalentTo(0))
   236  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
   237  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
   238  	Expect(txn.Description).To(Equal(description))
   239  	checkRecordedValues(txn.Values, []RecordedKVPair{
   240  		{Key: prefixA + baseValue1, Value: utils.RecordProtoMessage(test.NewArrayValue("item2")), Origin: FromNB},
   241  		{Key: prefixB + baseValue2, Value: utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")), Origin: FromNB},
   242  		{Key: prefixC + baseValue3, Value: utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")), Origin: FromNB},
   243  	})
   244  
   245  	txnOps := RecordedTxnOps{
   246  		{
   247  			Operation: TxnOperation_CREATE,
   248  			Key:       prefixA + baseValue1,
   249  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item2")),
   250  			PrevState: ValueState_NONEXISTENT,
   251  			NewState:  ValueState_CONFIGURED,
   252  		},
   253  		{
   254  			Operation: TxnOperation_CREATE,
   255  			Key:       prefixA + baseValue1 + "/item2",
   256  			IsDerived: true,
   257  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
   258  			PrevState: ValueState_NONEXISTENT,
   259  			NewState:  ValueState_CONFIGURED,
   260  		},
   261  		{
   262  			Operation: TxnOperation_CREATE,
   263  			Key:       prefixB + baseValue2,
   264  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
   265  			PrevState: ValueState_NONEXISTENT,
   266  			NewState:  ValueState_CONFIGURED,
   267  		},
   268  		{
   269  			Operation: TxnOperation_CREATE,
   270  			Key:       prefixB + baseValue2 + "/item1",
   271  			IsDerived: true,
   272  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
   273  			PrevState: ValueState_NONEXISTENT,
   274  			NewState:  ValueState_CONFIGURED,
   275  		},
   276  		{
   277  			Operation: TxnOperation_CREATE,
   278  			Key:       prefixB + baseValue2 + "/item2",
   279  			IsDerived: true,
   280  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
   281  			PrevState: ValueState_NONEXISTENT,
   282  			NewState:  ValueState_PENDING,
   283  			NOOP:      true,
   284  		},
   285  		{
   286  			Operation: TxnOperation_CREATE,
   287  			Key:       prefixC + baseValue3,
   288  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
   289  			PrevState: ValueState_NONEXISTENT,
   290  			NewState:  ValueState_CONFIGURED,
   291  		},
   292  		{
   293  			Operation: TxnOperation_CREATE,
   294  			Key:       prefixC + baseValue3 + "/item1",
   295  			IsDerived: true,
   296  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
   297  			PrevState: ValueState_NONEXISTENT,
   298  			NewState:  ValueState_CONFIGURED,
   299  		},
   300  		{
   301  			Operation: TxnOperation_CREATE,
   302  			Key:       prefixC + baseValue3 + "/item2",
   303  			IsDerived: true,
   304  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
   305  			PrevState: ValueState_NONEXISTENT,
   306  			NewState:  ValueState_CONFIGURED,
   307  		},
   308  	}
   309  	checkTxnOperations(txn.Planned, txnOps)
   310  	checkTxnOperations(txn.Executed, txnOps)
   311  
   312  	// check flag stats
   313  	graphR := scheduler.graph.Read()
   314  	errorStats := graphR.GetFlagStats(ErrorFlagIndex, nil)
   315  	Expect(errorStats.TotalCount).To(BeEquivalentTo(0))
   316  	pendingStats := graphR.GetFlagStats(UnavailValueFlagIndex, nil)
   317  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(1))
   318  	derivedStats := graphR.GetFlagStats(DerivedFlagIndex, nil)
   319  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(5))
   320  	lastUpdateStats := graphR.GetFlagStats(LastUpdateFlagIndex, nil)
   321  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(8))
   322  	descriptorStats := graphR.GetFlagStats(DescriptorFlagIndex, nil)
   323  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(8))
   324  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
   325  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(2))
   326  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor2Name))
   327  	Expect(descriptorStats.PerValueCount[descriptor2Name]).To(BeEquivalentTo(3))
   328  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor3Name))
   329  	Expect(descriptorStats.PerValueCount[descriptor3Name]).To(BeEquivalentTo(3))
   330  	valueStateStats := graphR.GetFlagStats(ValueStateFlagIndex, nil)
   331  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(8))
   332  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_CONFIGURED.String()))
   333  	Expect(valueStateStats.PerValueCount[ValueState_CONFIGURED.String()]).To(BeEquivalentTo(7))
   334  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
   335  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(1))
   336  	graphR.Release()
   337  
   338  	// check value dumps
   339  	views := []View{NBView, SBView, CachedView}
   340  	for _, view := range views {
   341  		// descriptor1
   342  		expValues := []KVWithMetadata{
   343  			{Key: prefixA + baseValue1, Value: test.NewArrayValue("item2"), Origin: FromNB, Metadata: &test.OnlyInteger{Integer: 0}},
   344  		}
   345  		dumpedValues, err := scheduler.DumpValuesByKeyPrefix(prefixA, view)
   346  		Expect(err).To(BeNil())
   347  		checkValues(dumpedValues, expValues)
   348  		dumpedValues, err = scheduler.DumpValuesByDescriptor(descriptor1Name, view)
   349  		Expect(err).To(BeNil())
   350  		checkValues(dumpedValues, expValues)
   351  		// descriptor2
   352  		expValues = []KVWithMetadata{
   353  			{Key: prefixB + baseValue2, Value: test.NewArrayValue("item1", "item2"), Origin: FromNB, Metadata: &test.OnlyInteger{Integer: 0}},
   354  		}
   355  		dumpedValues, err = scheduler.DumpValuesByKeyPrefix(prefixB, view)
   356  		Expect(err).To(BeNil())
   357  		checkValues(dumpedValues, expValues)
   358  		dumpedValues, err = scheduler.DumpValuesByDescriptor(descriptor2Name, view)
   359  		Expect(err).To(BeNil())
   360  		checkValues(dumpedValues, expValues)
   361  		// descriptor3
   362  		expValues = []KVWithMetadata{
   363  			{Key: prefixC + baseValue3, Value: test.NewArrayValue("item1", "item2"), Origin: FromNB, Metadata: &test.OnlyInteger{Integer: 0}},
   364  		}
   365  		dumpedValues, err = scheduler.DumpValuesByKeyPrefix(prefixC, view)
   366  		Expect(err).To(BeNil())
   367  		checkValues(dumpedValues, expValues)
   368  		dumpedValues, err = scheduler.DumpValuesByDescriptor(descriptor3Name, view)
   369  		Expect(err).To(BeNil())
   370  		checkValues(dumpedValues, expValues)
   371  	}
   372  	mockSB.PopHistoryOfOps() // remove Retrieve-s from the history
   373  
   374  	// check value states
   375  	status := scheduler.GetValueStatus(prefixA + baseValue1)
   376  	Expect(status).ToNot(BeNil())
   377  	checkBaseValueStatus(status, &BaseValueStatus{
   378  		Value: &ValueStatus{
   379  			Key:           prefixA + baseValue1,
   380  			State:         ValueState_CONFIGURED,
   381  			LastOperation: TxnOperation_CREATE,
   382  		},
   383  		DerivedValues: []*ValueStatus{
   384  			{
   385  				Key:           prefixA + baseValue1 + "/item2",
   386  				State:         ValueState_CONFIGURED,
   387  				LastOperation: TxnOperation_CREATE,
   388  			},
   389  		},
   390  	})
   391  	status = scheduler.GetValueStatus(prefixB + baseValue2)
   392  	Expect(status).ToNot(BeNil())
   393  	checkBaseValueStatus(status, &BaseValueStatus{
   394  		Value: &ValueStatus{
   395  			Key:           prefixB + baseValue2,
   396  			State:         ValueState_CONFIGURED,
   397  			LastOperation: TxnOperation_CREATE,
   398  		},
   399  		DerivedValues: []*ValueStatus{
   400  			{
   401  				Key:           prefixB + baseValue2 + "/item1",
   402  				State:         ValueState_CONFIGURED,
   403  				LastOperation: TxnOperation_CREATE,
   404  			},
   405  			{
   406  				Key:           prefixB + baseValue2 + "/item2",
   407  				State:         ValueState_PENDING,
   408  				LastOperation: TxnOperation_CREATE,
   409  				Details:       []string{prefixA + baseValue1 + "/item1"},
   410  			},
   411  		},
   412  	})
   413  	status = scheduler.GetValueStatus(prefixC + baseValue3)
   414  	Expect(status).ToNot(BeNil())
   415  	checkBaseValueStatus(status, &BaseValueStatus{
   416  		Value: &ValueStatus{
   417  			Key:           prefixC + baseValue3,
   418  			State:         ValueState_CONFIGURED,
   419  			LastOperation: TxnOperation_CREATE,
   420  		},
   421  		DerivedValues: []*ValueStatus{
   422  			{
   423  				Key:           prefixC + baseValue3 + "/item1",
   424  				State:         ValueState_CONFIGURED,
   425  				LastOperation: TxnOperation_CREATE,
   426  			},
   427  			{
   428  				Key:           prefixC + baseValue3 + "/item2",
   429  				State:         ValueState_CONFIGURED,
   430  				LastOperation: TxnOperation_CREATE,
   431  			},
   432  		},
   433  	})
   434  
   435  	// run 2nd non-resync transaction
   436  	startTime = time.Now()
   437  	schedulerTxn2 := scheduler.StartNBTransaction()
   438  	schedulerTxn2.SetValue(prefixC+baseValue3, test.NewArrayValue("item1"))
   439  	schedulerTxn2.SetValue(prefixA+baseValue1, test.NewArrayValue("item1"))
   440  	seqNum, err = schedulerTxn2.Commit(testCtx)
   441  	stopTime = time.Now()
   442  	Expect(seqNum).To(BeEquivalentTo(1))
   443  	Expect(err).ShouldNot(HaveOccurred())
   444  
   445  	// check the state of SB
   446  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
   447  	// -> base value 1
   448  	value = mockSB.GetValue(prefixA + baseValue1)
   449  	Expect(value).ToNot(BeNil())
   450  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1"))).To(BeTrue())
   451  	Expect(value.Metadata).ToNot(BeNil())
   452  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   453  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   454  	// -> item1 derived from base value was added
   455  	value = mockSB.GetValue(prefixA + baseValue1 + "/item1")
   456  	Expect(value).ToNot(BeNil())
   457  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   458  	Expect(value.Metadata).To(BeNil())
   459  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   460  	// -> item2 derived from base value 1 was deleted
   461  	value = mockSB.GetValue(prefixA + baseValue1 + "/item2")
   462  	Expect(value).To(BeNil())
   463  	// -> base value 2
   464  	value = mockSB.GetValue(prefixB + baseValue2)
   465  	Expect(value).ToNot(BeNil())
   466  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1", "item2"))).To(BeTrue())
   467  	Expect(value.Metadata).ToNot(BeNil())
   468  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   469  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   470  	// -> item1 derived from base value 2
   471  	value = mockSB.GetValue(prefixB + baseValue2 + "/item1")
   472  	Expect(value).ToNot(BeNil())
   473  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   474  	Expect(value.Metadata).To(BeNil())
   475  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   476  	// -> item2 derived from base value 2 is no longer pending
   477  	value = mockSB.GetValue(prefixB + baseValue2 + "/item2")
   478  	Expect(value).ToNot(BeNil())
   479  	Expect(proto.Equal(value.Value, test.NewStringValue("item2"))).To(BeTrue())
   480  	Expect(value.Metadata).To(BeNil())
   481  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   482  	// -> base value 3
   483  	value = mockSB.GetValue(prefixC + baseValue3)
   484  	Expect(value).ToNot(BeNil())
   485  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1"))).To(BeTrue())
   486  	Expect(value.Metadata).ToNot(BeNil())
   487  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(1))
   488  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   489  	// -> item1 derived from base value 3
   490  	value = mockSB.GetValue(prefixC + baseValue3 + "/item1")
   491  	Expect(value).ToNot(BeNil())
   492  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   493  	Expect(value.Metadata).To(BeNil())
   494  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   495  	// -> item2 derived from base value 3 was deleted
   496  	value = mockSB.GetValue(prefixC + baseValue3 + "/item2")
   497  	Expect(value).To(BeNil())
   498  
   499  	// check metadata
   500  	metadata, exists = nameToInteger1.LookupByName(baseValue1)
   501  	Expect(exists).To(BeTrue())
   502  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   503  	metadata, exists = nameToInteger2.LookupByName(baseValue2)
   504  	Expect(exists).To(BeTrue())
   505  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   506  	metadata, exists = nameToInteger3.LookupByName(baseValue3)
   507  	Expect(exists).To(BeTrue())
   508  	Expect(metadata.GetInteger()).To(BeEquivalentTo(1)) // re-created
   509  
   510  	// check operations executed in SB
   511  	opHistory = mockSB.PopHistoryOfOps()
   512  	Expect(opHistory).To(HaveLen(9))
   513  	operation = opHistory[0]
   514  	Expect(operation.OpType).To(Equal(test.MockDelete))
   515  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   516  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
   517  	Expect(operation.Err).To(BeNil())
   518  	operation = opHistory[1]
   519  	Expect(operation.OpType).To(Equal(test.MockDelete))
   520  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   521  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item2"))
   522  	Expect(operation.Err).To(BeNil())
   523  	operation = opHistory[2]
   524  	Expect(operation.OpType).To(Equal(test.MockDelete))
   525  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   526  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
   527  	Expect(operation.Err).To(BeNil())
   528  	operation = opHistory[3]
   529  	Expect(operation.OpType).To(Equal(test.MockCreate))
   530  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   531  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
   532  	Expect(operation.Err).To(BeNil())
   533  	operation = opHistory[4]
   534  	Expect(operation.OpType).To(Equal(test.MockCreate))
   535  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   536  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
   537  	Expect(operation.Err).To(BeNil())
   538  	operation = opHistory[5]
   539  	Expect(operation.OpType).To(Equal(test.MockDelete))
   540  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   541  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item2"))
   542  	Expect(operation.Err).To(BeNil())
   543  	operation = opHistory[6]
   544  	Expect(operation.OpType).To(Equal(test.MockUpdate))
   545  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   546  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
   547  	Expect(operation.Err).To(BeNil())
   548  	operation = opHistory[7]
   549  	Expect(operation.OpType).To(Equal(test.MockCreate))
   550  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   551  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item1"))
   552  	Expect(operation.Err).To(BeNil())
   553  	operation = opHistory[8]
   554  	Expect(operation.OpType).To(Equal(test.MockCreate))
   555  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor2Name))
   556  	Expect(operation.Key).To(BeEquivalentTo(prefixB + baseValue2 + "/item2"))
   557  	Expect(operation.Err).To(BeNil())
   558  
   559  	// check transaction operations
   560  	txnHistory = scheduler.GetTransactionHistory(startTime, stopTime) // first txn not included
   561  	Expect(txnHistory).To(HaveLen(1))
   562  	txn = txnHistory[0]
   563  	Expect(txn.PreRecord).To(BeFalse())
   564  	Expect(txn.Start.After(startTime)).To(BeTrue())
   565  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
   566  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
   567  	Expect(txn.SeqNum).To(BeEquivalentTo(1))
   568  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
   569  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
   570  	Expect(txn.Description).To(BeEmpty())
   571  	checkRecordedValues(txn.Values, []RecordedKVPair{
   572  		{Key: prefixA + baseValue1, Value: utils.RecordProtoMessage(test.NewArrayValue("item1")), Origin: FromNB},
   573  		{Key: prefixC + baseValue3, Value: utils.RecordProtoMessage(test.NewArrayValue("item1")), Origin: FromNB},
   574  	})
   575  
   576  	txnOps = RecordedTxnOps{
   577  		{
   578  			Operation:  TxnOperation_DELETE,
   579  			Key:        prefixC + baseValue3 + "/item1",
   580  			IsDerived:  true,
   581  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
   582  			PrevState:  ValueState_CONFIGURED,
   583  			NewState:   ValueState_REMOVED,
   584  			IsRecreate: true,
   585  		},
   586  		{
   587  			Operation:  TxnOperation_DELETE,
   588  			Key:        prefixC + baseValue3 + "/item2",
   589  			IsDerived:  true,
   590  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
   591  			PrevState:  ValueState_CONFIGURED,
   592  			NewState:   ValueState_REMOVED,
   593  			IsRecreate: true,
   594  		},
   595  		{
   596  			Operation:  TxnOperation_DELETE,
   597  			Key:        prefixC + baseValue3,
   598  			PrevValue:  utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
   599  			PrevState:  ValueState_CONFIGURED,
   600  			NewState:   ValueState_REMOVED,
   601  			IsRecreate: true,
   602  		},
   603  		{
   604  			Operation:  TxnOperation_CREATE,
   605  			Key:        prefixC + baseValue3,
   606  			NewValue:   utils.RecordProtoMessage(test.NewArrayValue("item1")),
   607  			PrevState:  ValueState_REMOVED,
   608  			NewState:   ValueState_CONFIGURED,
   609  			IsRecreate: true,
   610  		},
   611  		{
   612  			Operation:  TxnOperation_CREATE,
   613  			Key:        prefixC + baseValue3 + "/item1",
   614  			IsDerived:  true,
   615  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1")),
   616  			PrevState:  ValueState_NONEXISTENT, // TODO: derived value removed from the graph, ok?
   617  			NewState:   ValueState_CONFIGURED,
   618  			IsRecreate: true,
   619  		},
   620  		{
   621  			Operation: TxnOperation_DELETE,
   622  			Key:       prefixA + baseValue1 + "/item2",
   623  			IsDerived: true,
   624  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item2")),
   625  			PrevState: ValueState_CONFIGURED,
   626  			NewState:  ValueState_REMOVED,
   627  		},
   628  		{
   629  			Operation: TxnOperation_UPDATE,
   630  			Key:       prefixA + baseValue1,
   631  			PrevValue: utils.RecordProtoMessage(test.NewArrayValue("item2")),
   632  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item1")),
   633  			PrevState: ValueState_CONFIGURED,
   634  			NewState:  ValueState_CONFIGURED,
   635  		},
   636  		{
   637  			Operation: TxnOperation_CREATE,
   638  			Key:       prefixA + baseValue1 + "/item1",
   639  			IsDerived: true,
   640  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
   641  			PrevState: ValueState_NONEXISTENT,
   642  			NewState:  ValueState_CONFIGURED,
   643  		},
   644  		{
   645  			Operation: TxnOperation_CREATE,
   646  			Key:       prefixB + baseValue2 + "/item2",
   647  			IsDerived: true,
   648  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item2")),
   649  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
   650  			PrevState: ValueState_PENDING,
   651  			NewState:  ValueState_CONFIGURED,
   652  		},
   653  	}
   654  	checkTxnOperations(txn.Planned, txnOps)
   655  	checkTxnOperations(txn.Executed, txnOps)
   656  
   657  	// check value states
   658  	status = scheduler.GetValueStatus(prefixA + baseValue1)
   659  	Expect(status).ToNot(BeNil())
   660  	checkBaseValueStatus(status, &BaseValueStatus{
   661  		Value: &ValueStatus{
   662  			Key:           prefixA + baseValue1,
   663  			State:         ValueState_CONFIGURED,
   664  			LastOperation: TxnOperation_UPDATE,
   665  		},
   666  		DerivedValues: []*ValueStatus{
   667  			{
   668  				Key:           prefixA + baseValue1 + "/item1",
   669  				State:         ValueState_CONFIGURED,
   670  				LastOperation: TxnOperation_CREATE,
   671  			},
   672  		},
   673  	})
   674  	status = scheduler.GetValueStatus(prefixB + baseValue2)
   675  	Expect(status).ToNot(BeNil())
   676  	checkBaseValueStatus(status, &BaseValueStatus{
   677  		Value: &ValueStatus{
   678  			Key:           prefixB + baseValue2,
   679  			State:         ValueState_CONFIGURED,
   680  			LastOperation: TxnOperation_CREATE,
   681  		},
   682  		DerivedValues: []*ValueStatus{
   683  			{
   684  				Key:           prefixB + baseValue2 + "/item1",
   685  				State:         ValueState_CONFIGURED,
   686  				LastOperation: TxnOperation_CREATE,
   687  			},
   688  			{
   689  				Key:           prefixB + baseValue2 + "/item2",
   690  				State:         ValueState_CONFIGURED,
   691  				LastOperation: TxnOperation_CREATE,
   692  			},
   693  		},
   694  	})
   695  	status = scheduler.GetValueStatus(prefixC + baseValue3)
   696  	Expect(status).ToNot(BeNil())
   697  	checkBaseValueStatus(status, &BaseValueStatus{
   698  		Value: &ValueStatus{
   699  			Key:           prefixC + baseValue3,
   700  			State:         ValueState_CONFIGURED,
   701  			LastOperation: TxnOperation_UPDATE,
   702  		},
   703  		DerivedValues: []*ValueStatus{
   704  			{
   705  				Key:           prefixC + baseValue3 + "/item1",
   706  				State:         ValueState_CONFIGURED,
   707  				LastOperation: TxnOperation_CREATE,
   708  			},
   709  		},
   710  	})
   711  
   712  	// check flag stats
   713  	graphR = scheduler.graph.Read()
   714  	errorStats = graphR.GetFlagStats(ErrorFlagIndex, nil)
   715  	Expect(errorStats.TotalCount).To(BeEquivalentTo(0))
   716  	pendingStats = graphR.GetFlagStats(UnavailValueFlagIndex, nil)
   717  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(1))
   718  	derivedStats = graphR.GetFlagStats(DerivedFlagIndex, nil)
   719  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(8))
   720  	lastUpdateStats = graphR.GetFlagStats(LastUpdateFlagIndex, nil)
   721  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(13))
   722  	descriptorStats = graphR.GetFlagStats(DescriptorFlagIndex, nil)
   723  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(13))
   724  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
   725  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(4))
   726  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor2Name))
   727  	Expect(descriptorStats.PerValueCount[descriptor2Name]).To(BeEquivalentTo(4))
   728  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor3Name))
   729  	Expect(descriptorStats.PerValueCount[descriptor3Name]).To(BeEquivalentTo(5))
   730  	valueStateStats = graphR.GetFlagStats(ValueStateFlagIndex, nil)
   731  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(13))
   732  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_CONFIGURED.String()))
   733  	Expect(valueStateStats.PerValueCount[ValueState_CONFIGURED.String()]).To(BeEquivalentTo(12))
   734  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
   735  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(1))
   736  	graphR.Release()
   737  
   738  	// close scheduler
   739  	err = scheduler.Close()
   740  	Expect(err).To(BeNil())
   741  }
   742  
   743  func TestDataChangeTransactionWithRevert(t *testing.T) {
   744  	RegisterTestingT(t)
   745  
   746  	// prepare KV Scheduler
   747  	scheduler := NewPlugin(UseDeps(func(deps *Deps) {
   748  		deps.HTTPHandlers = nil
   749  	}))
   750  	err := scheduler.Init()
   751  	Expect(err).To(BeNil())
   752  
   753  	// prepare mocks
   754  	mockSB := test.NewMockSouthbound()
   755  	// -> descriptor1:
   756  	descriptor1 := test.NewMockDescriptor(&KVDescriptor{
   757  		Name:          descriptor1Name,
   758  		NBKeyPrefix:   prefixA,
   759  		KeySelector:   prefixSelector(prefixA),
   760  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
   761  		DerivedValues: test.ArrayValueDerBuilder,
   762  		WithMetadata:  true,
   763  	}, mockSB, 0)
   764  	// -> descriptor2:
   765  	descriptor2 := test.NewMockDescriptor(&KVDescriptor{
   766  		Name:          descriptor2Name,
   767  		NBKeyPrefix:   prefixB,
   768  		KeySelector:   prefixSelector(prefixB),
   769  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
   770  		DerivedValues: test.ArrayValueDerBuilder,
   771  		Dependencies: func(key string, value proto.Message) []Dependency {
   772  			if key == prefixB+baseValue2+"/item1" {
   773  				depKey := prefixA + baseValue1
   774  				return []Dependency{
   775  					{Label: depKey, Key: depKey},
   776  				}
   777  			}
   778  			if key == prefixB+baseValue2+"/item2" {
   779  				depKey := prefixA + baseValue1 + "/item1"
   780  				return []Dependency{
   781  					{Label: depKey, Key: depKey},
   782  				}
   783  			}
   784  			return nil
   785  		},
   786  		WithMetadata:         true,
   787  		RetrieveDependencies: []string{descriptor1Name},
   788  	}, mockSB, 0)
   789  	// -> descriptor3:
   790  	descriptor3 := test.NewMockDescriptor(&KVDescriptor{
   791  		Name:          descriptor3Name,
   792  		NBKeyPrefix:   prefixC,
   793  		KeySelector:   prefixSelector(prefixC),
   794  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
   795  		DerivedValues: test.ArrayValueDerBuilder,
   796  		UpdateWithRecreate: func(key string, oldValue, newValue proto.Message, metadata Metadata) bool {
   797  			return key == prefixC+baseValue3
   798  		},
   799  		WithMetadata:         true,
   800  		RetrieveDependencies: []string{descriptor2Name},
   801  	}, mockSB, 0)
   802  
   803  	// register all 3 descriptors with the scheduler
   804  	scheduler.RegisterKVDescriptor(descriptor1)
   805  	scheduler.RegisterKVDescriptor(descriptor2)
   806  	scheduler.RegisterKVDescriptor(descriptor3)
   807  
   808  	// get metadata map created for each descriptor
   809  	metadataMap := scheduler.GetMetadataMap(descriptor1.Name)
   810  	nameToInteger1, withMetadataMap := metadataMap.(test.NameToInteger)
   811  	Expect(withMetadataMap).To(BeTrue())
   812  	metadataMap = scheduler.GetMetadataMap(descriptor2.Name)
   813  	nameToInteger2, withMetadataMap := metadataMap.(test.NameToInteger)
   814  	Expect(withMetadataMap).To(BeTrue())
   815  	metadataMap = scheduler.GetMetadataMap(descriptor3.Name)
   816  	nameToInteger3, withMetadataMap := metadataMap.(test.NameToInteger)
   817  	Expect(withMetadataMap).To(BeTrue())
   818  
   819  	// run 1st non-resync transaction against empty SB
   820  	schedulerTxn := scheduler.StartNBTransaction()
   821  	schedulerTxn.SetValue(prefixB+baseValue2, test.NewArrayValue("item1", "item2"))
   822  	schedulerTxn.SetValue(prefixA+baseValue1, test.NewArrayValue("item2"))
   823  	schedulerTxn.SetValue(prefixC+baseValue3, test.NewArrayValue("item1", "item2"))
   824  	seqNum, err := schedulerTxn.Commit(testCtx)
   825  	Expect(seqNum).To(BeEquivalentTo(0))
   826  	Expect(err).ShouldNot(HaveOccurred())
   827  	mockSB.PopHistoryOfOps()
   828  
   829  	// plan error before 2nd txn
   830  	failedModifyClb := func() {
   831  		mockSB.SetValue(prefixA+baseValue1, test.NewArrayValue(),
   832  			&test.OnlyInteger{Integer: 0}, FromNB, false)
   833  	}
   834  	mockSB.PlanError(prefixA+baseValue1, errors.New("failed to modify value"), failedModifyClb)
   835  
   836  	// subscribe to receive notifications about value state changes for prefixA
   837  	statusChan := make(chan *BaseValueStatus, 5)
   838  	scheduler.WatchValueStatus(statusChan, prefixSelector(prefixA))
   839  
   840  	// run 2nd non-resync transaction against empty SB that will fail and will be reverted
   841  	startTime := time.Now()
   842  	schedulerTxn2 := scheduler.StartNBTransaction()
   843  	schedulerTxn2.SetValue(prefixC+baseValue3, test.NewArrayValue("item1"))
   844  	schedulerTxn2.SetValue(prefixA+baseValue1, test.NewArrayValue("item1"))
   845  	seqNum, err = schedulerTxn2.Commit(WithRevert(testCtx))
   846  	stopTime := time.Now()
   847  	Expect(seqNum).To(BeEquivalentTo(1))
   848  	Expect(err).ToNot(BeNil())
   849  	txnErr := err.(*TransactionError)
   850  	Expect(txnErr.GetTxnInitError()).ShouldNot(HaveOccurred())
   851  	kvErrors := txnErr.GetKVErrors()
   852  	Expect(kvErrors).To(HaveLen(1))
   853  	Expect(kvErrors[0].Key).To(BeEquivalentTo(prefixA + baseValue1))
   854  	Expect(kvErrors[0].TxnOperation).To(BeEquivalentTo(TxnOperation_UPDATE))
   855  	Expect(kvErrors[0].Error.Error()).To(BeEquivalentTo("failed to modify value"))
   856  
   857  	// receive the status update with the value reverted back to the original
   858  	var valueStatus *BaseValueStatus
   859  	Eventually(statusChan, time.Second).Should(Receive(&valueStatus))
   860  	checkBaseValueStatus(valueStatus, &BaseValueStatus{
   861  		Value: &ValueStatus{
   862  			Key:           prefixA + baseValue1,
   863  			State:         ValueState_CONFIGURED,
   864  			LastOperation: TxnOperation_UPDATE,
   865  		},
   866  		DerivedValues: []*ValueStatus{
   867  			{
   868  				Key:           prefixA + baseValue1 + "/item2",
   869  				State:         ValueState_CONFIGURED,
   870  				LastOperation: TxnOperation_CREATE,
   871  			},
   872  		},
   873  	})
   874  
   875  	// check the state of SB
   876  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
   877  	// -> base value 1
   878  	value := mockSB.GetValue(prefixA + baseValue1)
   879  	Expect(value).ToNot(BeNil())
   880  	Expect(proto.Equal(value.Value, test.NewArrayValue("item2"))).To(BeTrue())
   881  	Expect(value.Metadata).ToNot(BeNil())
   882  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   883  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   884  	// -> item1 derived from base value was NOT added
   885  	value = mockSB.GetValue(prefixA + baseValue1 + "/item1")
   886  	Expect(value).To(BeNil())
   887  	// -> item2 derived from base value 1 was first deleted by then added back
   888  	value = mockSB.GetValue(prefixA + baseValue1 + "/item2")
   889  	Expect(value).ToNot(BeNil())
   890  	Expect(proto.Equal(value.Value, test.NewStringValue("item2"))).To(BeTrue())
   891  	Expect(value.Metadata).To(BeNil())
   892  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   893  	// -> base value 2
   894  	value = mockSB.GetValue(prefixB + baseValue2)
   895  	Expect(value).ToNot(BeNil())
   896  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1", "item2"))).To(BeTrue())
   897  	Expect(value.Metadata).ToNot(BeNil())
   898  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
   899  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   900  	// -> item1 derived from base value 2
   901  	value = mockSB.GetValue(prefixB + baseValue2 + "/item1")
   902  	Expect(value).ToNot(BeNil())
   903  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   904  	Expect(value.Metadata).To(BeNil())
   905  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   906  	// -> item2 derived from base value 2 is still pending
   907  	value = mockSB.GetValue(prefixB + baseValue2 + "/item2")
   908  	Expect(value).To(BeNil())
   909  	// -> base value 3 was reverted back to state after 1st txn
   910  	value = mockSB.GetValue(prefixC + baseValue3)
   911  	Expect(value).ToNot(BeNil())
   912  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1", "item2"))).To(BeTrue())
   913  	Expect(value.Metadata).ToNot(BeNil())
   914  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(2))
   915  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   916  	// -> item1 derived from base value 3
   917  	value = mockSB.GetValue(prefixC + baseValue3 + "/item1")
   918  	Expect(value).ToNot(BeNil())
   919  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
   920  	Expect(value.Metadata).To(BeNil())
   921  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   922  	// -> item2 derived from base value 3
   923  	value = mockSB.GetValue(prefixC + baseValue3 + "/item2")
   924  	Expect(value).ToNot(BeNil())
   925  	Expect(proto.Equal(value.Value, test.NewStringValue("item2"))).To(BeTrue())
   926  	Expect(value.Metadata).To(BeNil())
   927  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
   928  
   929  	// check metadata
   930  	metadata, exists := nameToInteger1.LookupByName(baseValue1)
   931  	Expect(exists).To(BeTrue())
   932  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   933  	metadata, exists = nameToInteger2.LookupByName(baseValue2)
   934  	Expect(exists).To(BeTrue())
   935  	Expect(metadata.GetInteger()).To(BeEquivalentTo(0))
   936  	metadata, exists = nameToInteger3.LookupByName(baseValue3)
   937  	Expect(exists).To(BeTrue())
   938  	Expect(metadata.GetInteger()).To(BeEquivalentTo(2)) // re-created twice
   939  
   940  	// check operations executed in SB during 2nd txn
   941  	opHistory := mockSB.PopHistoryOfOps()
   942  	Expect(opHistory).To(HaveLen(15))
   943  	operation := opHistory[0]
   944  	Expect(operation.OpType).To(Equal(test.MockDelete))
   945  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   946  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
   947  	Expect(operation.Err).To(BeNil())
   948  	operation = opHistory[1]
   949  	Expect(operation.OpType).To(Equal(test.MockDelete))
   950  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   951  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item2"))
   952  	Expect(operation.Err).To(BeNil())
   953  	operation = opHistory[2]
   954  	Expect(operation.OpType).To(Equal(test.MockDelete))
   955  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   956  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
   957  	Expect(operation.Err).To(BeNil())
   958  	operation = opHistory[3]
   959  	Expect(operation.OpType).To(Equal(test.MockCreate))
   960  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   961  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
   962  	Expect(operation.Err).To(BeNil())
   963  	operation = opHistory[4]
   964  	Expect(operation.OpType).To(Equal(test.MockCreate))
   965  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
   966  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
   967  	Expect(operation.Err).To(BeNil())
   968  	operation = opHistory[5]
   969  	Expect(operation.OpType).To(Equal(test.MockDelete))
   970  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   971  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item2"))
   972  	Expect(operation.Err).To(BeNil())
   973  	operation = opHistory[6]
   974  	Expect(operation.OpType).To(Equal(test.MockUpdate))
   975  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   976  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
   977  	Expect(operation.Err).ToNot(BeNil())
   978  	Expect(operation.Err.Error()).To(BeEquivalentTo("failed to modify value"))
   979  	// reverting:
   980  	operation = opHistory[7] // refresh failed value
   981  	Expect(operation.OpType).To(Equal(test.MockRetrieve))
   982  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   983  	checkValues(operation.CorrelateRetrieve, []KVWithMetadata{
   984  		{
   985  			Key:      prefixA + baseValue1,
   986  			Value:    test.NewArrayValue("item1"),
   987  			Metadata: &test.OnlyInteger{Integer: 0},
   988  			Origin:   FromNB,
   989  		},
   990  	})
   991  	operation = opHistory[8]
   992  	Expect(operation.OpType).To(Equal(test.MockUpdate))
   993  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   994  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
   995  	Expect(operation.Err).To(BeNil())
   996  	operation = opHistory[9]
   997  	Expect(operation.OpType).To(Equal(test.MockCreate))
   998  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
   999  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item2"))
  1000  	Expect(operation.Err).To(BeNil())
  1001  	operation = opHistory[10]
  1002  	Expect(operation.OpType).To(Equal(test.MockDelete))
  1003  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
  1004  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
  1005  	Expect(operation.Err).To(BeNil())
  1006  	operation = opHistory[11]
  1007  	Expect(operation.OpType).To(Equal(test.MockDelete))
  1008  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
  1009  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
  1010  	Expect(operation.Err).To(BeNil())
  1011  	operation = opHistory[12]
  1012  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1013  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
  1014  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3))
  1015  	Expect(operation.Err).To(BeNil())
  1016  	operation = opHistory[13]
  1017  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1018  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
  1019  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item1"))
  1020  	Expect(operation.Err).To(BeNil())
  1021  	operation = opHistory[14]
  1022  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1023  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor3Name))
  1024  	Expect(operation.Key).To(BeEquivalentTo(prefixC + baseValue3 + "/item2"))
  1025  	Expect(operation.Err).To(BeNil())
  1026  
  1027  	// check transaction operations
  1028  	txnHistory := scheduler.GetTransactionHistory(startTime, time.Now())
  1029  	Expect(txnHistory).To(HaveLen(1))
  1030  	txn := txnHistory[0]
  1031  	Expect(txn.PreRecord).To(BeFalse())
  1032  	Expect(txn.Start.After(startTime)).To(BeTrue())
  1033  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  1034  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  1035  	Expect(txn.SeqNum).To(BeEquivalentTo(1))
  1036  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  1037  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  1038  	Expect(txn.Description).To(BeEmpty())
  1039  	checkRecordedValues(txn.Values, []RecordedKVPair{
  1040  		{Key: prefixA + baseValue1, Value: utils.RecordProtoMessage(test.NewArrayValue("item1")), Origin: FromNB},
  1041  		{Key: prefixC + baseValue3, Value: utils.RecordProtoMessage(test.NewArrayValue("item1")), Origin: FromNB},
  1042  	})
  1043  
  1044  	// planned operations
  1045  	txnOps := RecordedTxnOps{
  1046  		{
  1047  			Operation:  TxnOperation_DELETE,
  1048  			Key:        prefixC + baseValue3 + "/item1",
  1049  			IsDerived:  true,
  1050  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
  1051  			PrevState:  ValueState_CONFIGURED,
  1052  			NewState:   ValueState_REMOVED,
  1053  			IsRecreate: true,
  1054  		},
  1055  		{
  1056  			Operation:  TxnOperation_DELETE,
  1057  			Key:        prefixC + baseValue3 + "/item2",
  1058  			IsDerived:  true,
  1059  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
  1060  			PrevState:  ValueState_CONFIGURED,
  1061  			NewState:   ValueState_REMOVED,
  1062  			IsRecreate: true,
  1063  		},
  1064  		{
  1065  			Operation:  TxnOperation_DELETE,
  1066  			Key:        prefixC + baseValue3,
  1067  			PrevValue:  utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
  1068  			PrevState:  ValueState_CONFIGURED,
  1069  			NewState:   ValueState_REMOVED,
  1070  			IsRecreate: true,
  1071  		},
  1072  		{
  1073  			Operation:  TxnOperation_CREATE,
  1074  			Key:        prefixC + baseValue3,
  1075  			NewValue:   utils.RecordProtoMessage(test.NewArrayValue("item1")),
  1076  			PrevState:  ValueState_REMOVED,
  1077  			NewState:   ValueState_CONFIGURED,
  1078  			IsRecreate: true,
  1079  		},
  1080  		{
  1081  			Operation:  TxnOperation_CREATE,
  1082  			Key:        prefixC + baseValue3 + "/item1",
  1083  			IsDerived:  true,
  1084  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1")),
  1085  			PrevState:  ValueState_NONEXISTENT,
  1086  			NewState:   ValueState_CONFIGURED,
  1087  			IsRecreate: true,
  1088  		},
  1089  		{
  1090  			Operation: TxnOperation_DELETE,
  1091  			Key:       prefixA + baseValue1 + "/item2",
  1092  			IsDerived: true,
  1093  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item2")),
  1094  			PrevState: ValueState_CONFIGURED,
  1095  			NewState:  ValueState_REMOVED,
  1096  		},
  1097  		{
  1098  			Operation: TxnOperation_UPDATE,
  1099  			Key:       prefixA + baseValue1,
  1100  			PrevValue: utils.RecordProtoMessage(test.NewArrayValue("item2")),
  1101  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item1")),
  1102  			PrevState: ValueState_CONFIGURED,
  1103  			NewState:  ValueState_CONFIGURED,
  1104  		},
  1105  		{
  1106  			Operation: TxnOperation_CREATE,
  1107  			Key:       prefixA + baseValue1 + "/item1",
  1108  			IsDerived: true,
  1109  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
  1110  			PrevState: ValueState_NONEXISTENT,
  1111  			NewState:  ValueState_CONFIGURED,
  1112  		},
  1113  		{
  1114  			Operation: TxnOperation_CREATE,
  1115  			Key:       prefixB + baseValue2 + "/item2",
  1116  			IsDerived: true,
  1117  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item2")),
  1118  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
  1119  			PrevState: ValueState_PENDING,
  1120  			NewState:  ValueState_CONFIGURED,
  1121  		},
  1122  	}
  1123  	checkTxnOperations(txn.Planned, txnOps)
  1124  
  1125  	// executed operations
  1126  	txnOps = RecordedTxnOps{
  1127  		{
  1128  			Operation:  TxnOperation_DELETE,
  1129  			Key:        prefixC + baseValue3 + "/item1",
  1130  			IsDerived:  true,
  1131  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
  1132  			PrevState:  ValueState_CONFIGURED,
  1133  			NewState:   ValueState_REMOVED,
  1134  			IsRecreate: true,
  1135  		},
  1136  		{
  1137  			Operation:  TxnOperation_DELETE,
  1138  			Key:        prefixC + baseValue3 + "/item2",
  1139  			IsDerived:  true,
  1140  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
  1141  			PrevState:  ValueState_CONFIGURED,
  1142  			NewState:   ValueState_REMOVED,
  1143  			IsRecreate: true,
  1144  		},
  1145  		{
  1146  			Operation:  TxnOperation_DELETE,
  1147  			Key:        prefixC + baseValue3,
  1148  			PrevValue:  utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
  1149  			PrevState:  ValueState_CONFIGURED,
  1150  			NewState:   ValueState_REMOVED,
  1151  			IsRecreate: true,
  1152  		},
  1153  		{
  1154  			Operation:  TxnOperation_CREATE,
  1155  			Key:        prefixC + baseValue3,
  1156  			NewValue:   utils.RecordProtoMessage(test.NewArrayValue("item1")),
  1157  			PrevState:  ValueState_REMOVED,
  1158  			NewState:   ValueState_CONFIGURED,
  1159  			IsRecreate: true,
  1160  		},
  1161  		{
  1162  			Operation:  TxnOperation_CREATE,
  1163  			Key:        prefixC + baseValue3 + "/item1",
  1164  			IsDerived:  true,
  1165  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1")),
  1166  			PrevState:  ValueState_NONEXISTENT,
  1167  			NewState:   ValueState_CONFIGURED,
  1168  			IsRecreate: true,
  1169  		},
  1170  		{
  1171  			Operation: TxnOperation_DELETE,
  1172  			Key:       prefixA + baseValue1 + "/item2",
  1173  			IsDerived: true,
  1174  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item2")),
  1175  			PrevState: ValueState_CONFIGURED,
  1176  			NewState:  ValueState_REMOVED,
  1177  		},
  1178  		{
  1179  			Operation: TxnOperation_UPDATE,
  1180  			Key:       prefixA + baseValue1,
  1181  			PrevValue: utils.RecordProtoMessage(test.NewArrayValue("item2")),
  1182  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item1")),
  1183  			PrevState: ValueState_CONFIGURED,
  1184  			NewState:  ValueState_FAILED,
  1185  			NewErr:    errors.New("failed to modify value"),
  1186  		},
  1187  		// reverting:
  1188  		{
  1189  			Operation: TxnOperation_UPDATE,
  1190  			Key:       prefixA + baseValue1,
  1191  			PrevValue: utils.RecordProtoMessage(test.NewArrayValue()),
  1192  			NewValue:  utils.RecordProtoMessage(test.NewArrayValue("item2")),
  1193  			PrevState: ValueState_FAILED,
  1194  			NewState:  ValueState_CONFIGURED,
  1195  			PrevErr:   errors.New("failed to modify value"),
  1196  			IsRevert:  true,
  1197  		},
  1198  		{
  1199  			Operation: TxnOperation_CREATE,
  1200  			Key:       prefixA + baseValue1 + "/item2",
  1201  			IsDerived: true,
  1202  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
  1203  			PrevState: ValueState_NONEXISTENT,
  1204  			NewState:  ValueState_CONFIGURED,
  1205  			IsRevert:  true,
  1206  		},
  1207  		{
  1208  			Operation:  TxnOperation_DELETE,
  1209  			Key:        prefixC + baseValue3 + "/item1",
  1210  			IsDerived:  true,
  1211  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1")),
  1212  			PrevState:  ValueState_CONFIGURED,
  1213  			NewState:   ValueState_REMOVED,
  1214  			IsRevert:   true,
  1215  			IsRecreate: true,
  1216  		},
  1217  		{
  1218  			Operation:  TxnOperation_DELETE,
  1219  			Key:        prefixC + baseValue3,
  1220  			PrevValue:  utils.RecordProtoMessage(test.NewArrayValue("item1")),
  1221  			PrevState:  ValueState_CONFIGURED,
  1222  			NewState:   ValueState_REMOVED,
  1223  			IsRevert:   true,
  1224  			IsRecreate: true,
  1225  		},
  1226  		{
  1227  			Operation:  TxnOperation_CREATE,
  1228  			Key:        prefixC + baseValue3,
  1229  			NewValue:   utils.RecordProtoMessage(test.NewArrayValue("item1", "item2")),
  1230  			PrevState:  ValueState_REMOVED,
  1231  			NewState:   ValueState_CONFIGURED,
  1232  			IsRevert:   true,
  1233  			IsRecreate: true,
  1234  		},
  1235  		{
  1236  			Operation:  TxnOperation_CREATE,
  1237  			Key:        prefixC + baseValue3 + "/item1",
  1238  			IsDerived:  true,
  1239  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1")),
  1240  			PrevState:  ValueState_NONEXISTENT,
  1241  			NewState:   ValueState_CONFIGURED,
  1242  			IsRevert:   true,
  1243  			IsRecreate: true,
  1244  		},
  1245  		{
  1246  			Operation: TxnOperation_CREATE,
  1247  			Key:       prefixC + baseValue3 + "/item2",
  1248  			IsDerived: true,
  1249  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("item2")),
  1250  			PrevState: ValueState_NONEXISTENT,
  1251  			NewState:  ValueState_CONFIGURED,
  1252  			IsRevert:  true,
  1253  		},
  1254  	}
  1255  	checkTxnOperations(txn.Executed, txnOps)
  1256  
  1257  	// check flag stats
  1258  	graphR := scheduler.graph.Read()
  1259  	errorStats := graphR.GetFlagStats(ErrorFlagIndex, nil)
  1260  	Expect(errorStats.TotalCount).To(BeEquivalentTo(1))
  1261  	pendingStats := graphR.GetFlagStats(UnavailValueFlagIndex, nil)
  1262  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(1))
  1263  	derivedStats := graphR.GetFlagStats(DerivedFlagIndex, nil)
  1264  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(9))
  1265  	lastUpdateStats := graphR.GetFlagStats(LastUpdateFlagIndex, nil)
  1266  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(16))
  1267  	descriptorStats := graphR.GetFlagStats(DescriptorFlagIndex, nil)
  1268  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(16))
  1269  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
  1270  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(5))
  1271  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor2Name))
  1272  	Expect(descriptorStats.PerValueCount[descriptor2Name]).To(BeEquivalentTo(3))
  1273  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor3Name))
  1274  	Expect(descriptorStats.PerValueCount[descriptor3Name]).To(BeEquivalentTo(8))
  1275  	valueStateStats := graphR.GetFlagStats(ValueStateFlagIndex, nil)
  1276  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(16))
  1277  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_CONFIGURED.String()))
  1278  	Expect(valueStateStats.PerValueCount[ValueState_CONFIGURED.String()]).To(BeEquivalentTo(14))
  1279  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_FAILED.String()))
  1280  	Expect(valueStateStats.PerValueCount[ValueState_FAILED.String()]).To(BeEquivalentTo(1))
  1281  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
  1282  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(1))
  1283  	graphR.Release()
  1284  
  1285  	// close scheduler
  1286  	err = scheduler.Close()
  1287  	Expect(err).To(BeNil())
  1288  }
  1289  
  1290  func TestDependencyCycles(t *testing.T) {
  1291  	RegisterTestingT(t)
  1292  
  1293  	// prepare KV Scheduler
  1294  	scheduler := NewPlugin(UseDeps(func(deps *Deps) {
  1295  		deps.HTTPHandlers = nil
  1296  	}))
  1297  	err := scheduler.Init()
  1298  	Expect(err).To(BeNil())
  1299  
  1300  	// prepare mocks
  1301  	mockSB := test.NewMockSouthbound()
  1302  	// -> descriptor:
  1303  	descriptor := test.NewMockDescriptor(&KVDescriptor{
  1304  		Name:            descriptor1Name,
  1305  		KeySelector:     prefixSelector(prefixA),
  1306  		NBKeyPrefix:     prefixA,
  1307  		ValueTypeName:   string(proto.MessageName(test.NewStringValue(""))),
  1308  		ValueComparator: test.StringValueComparator,
  1309  		Dependencies: func(key string, value proto.Message) []Dependency {
  1310  			if key == prefixA+baseValue1 {
  1311  				depKey := prefixA + baseValue2
  1312  				return []Dependency{
  1313  					{Label: depKey, Key: depKey},
  1314  				}
  1315  			}
  1316  			if key == prefixA+baseValue2 {
  1317  				depKey := prefixA + baseValue3
  1318  				return []Dependency{
  1319  					{Label: depKey, Key: depKey},
  1320  				}
  1321  			}
  1322  			if key == prefixA+baseValue3 {
  1323  				depKey1 := prefixA + baseValue1
  1324  				depKey2 := prefixA + baseValue4
  1325  				return []Dependency{
  1326  					{Label: depKey1, Key: depKey1},
  1327  					{Label: depKey2, Key: depKey2},
  1328  				}
  1329  			}
  1330  			return nil
  1331  		},
  1332  		WithMetadata: false,
  1333  	}, mockSB, 0, test.WithoutRetrieve)
  1334  
  1335  	// register the descriptor
  1336  	scheduler.RegisterKVDescriptor(descriptor)
  1337  
  1338  	// run non-resync transaction against empty SB
  1339  	startTime := time.Now()
  1340  	schedulerTxn := scheduler.StartNBTransaction()
  1341  	schedulerTxn.SetValue(prefixA+baseValue1, test.NewStringValue("base-value1-data"))
  1342  	schedulerTxn.SetValue(prefixA+baseValue2, test.NewStringValue("base-value2-data"))
  1343  	schedulerTxn.SetValue(prefixA+baseValue3, test.NewStringValue("base-value3-data"))
  1344  	description := "testing dependency cycles"
  1345  	seqNum, err := schedulerTxn.Commit(WithDescription(testCtx, description))
  1346  	stopTime := time.Now()
  1347  	Expect(seqNum).To(BeEquivalentTo(0))
  1348  	Expect(err).ShouldNot(HaveOccurred())
  1349  
  1350  	// check the state of SB
  1351  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
  1352  	Expect(mockSB.GetValues(nil)).To(HaveLen(0))
  1353  
  1354  	// check value states
  1355  	status := scheduler.GetValueStatus(prefixA + baseValue1)
  1356  	Expect(status).ToNot(BeNil())
  1357  	checkBaseValueStatus(status, &BaseValueStatus{
  1358  		Value: &ValueStatus{
  1359  			Key:           prefixA + baseValue1,
  1360  			State:         ValueState_PENDING,
  1361  			LastOperation: TxnOperation_CREATE,
  1362  			Details:       []string{prefixA + baseValue2},
  1363  		},
  1364  	})
  1365  	status = scheduler.GetValueStatus(prefixA + baseValue2)
  1366  	Expect(status).ToNot(BeNil())
  1367  	checkBaseValueStatus(status, &BaseValueStatus{
  1368  		Value: &ValueStatus{
  1369  			Key:           prefixA + baseValue2,
  1370  			State:         ValueState_PENDING,
  1371  			LastOperation: TxnOperation_CREATE,
  1372  			Details:       []string{prefixA + baseValue3},
  1373  		},
  1374  	})
  1375  	status = scheduler.GetValueStatus(prefixA + baseValue3)
  1376  	Expect(status).ToNot(BeNil())
  1377  	checkBaseValueStatus(status, &BaseValueStatus{
  1378  		Value: &ValueStatus{
  1379  			Key:           prefixA + baseValue3,
  1380  			State:         ValueState_PENDING,
  1381  			LastOperation: TxnOperation_CREATE,
  1382  			Details:       []string{prefixA + baseValue4, prefixA + baseValue1},
  1383  		},
  1384  	})
  1385  
  1386  	// check operations executed in SB
  1387  	opHistory := mockSB.PopHistoryOfOps()
  1388  	Expect(opHistory).To(HaveLen(0))
  1389  
  1390  	// check transaction operations
  1391  	txnHistory := scheduler.GetTransactionHistory(time.Time{}, time.Now())
  1392  	Expect(txnHistory).To(HaveLen(1))
  1393  	txn := txnHistory[0]
  1394  	Expect(txn.PreRecord).To(BeFalse())
  1395  	Expect(txn.Start.After(startTime)).To(BeTrue())
  1396  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  1397  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  1398  	Expect(txn.SeqNum).To(BeEquivalentTo(0))
  1399  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  1400  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  1401  	Expect(txn.Description).To(Equal(description))
  1402  	checkRecordedValues(txn.Values, []RecordedKVPair{
  1403  		{Key: prefixA + baseValue1, Value: utils.RecordProtoMessage(test.NewStringValue("base-value1-data")), Origin: FromNB},
  1404  		{Key: prefixA + baseValue2, Value: utils.RecordProtoMessage(test.NewStringValue("base-value2-data")), Origin: FromNB},
  1405  		{Key: prefixA + baseValue3, Value: utils.RecordProtoMessage(test.NewStringValue("base-value3-data")), Origin: FromNB},
  1406  	})
  1407  
  1408  	txnOps := RecordedTxnOps{
  1409  		{
  1410  			Operation: TxnOperation_CREATE,
  1411  			Key:       prefixA + baseValue1,
  1412  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1413  			PrevState: ValueState_NONEXISTENT,
  1414  			NewState:  ValueState_PENDING,
  1415  			NOOP:      true,
  1416  		},
  1417  		{
  1418  			Operation: TxnOperation_CREATE,
  1419  			Key:       prefixA + baseValue2,
  1420  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value2-data")),
  1421  			PrevState: ValueState_NONEXISTENT,
  1422  			NewState:  ValueState_PENDING,
  1423  			NOOP:      true,
  1424  		},
  1425  		{
  1426  			Operation: TxnOperation_CREATE,
  1427  			Key:       prefixA + baseValue3,
  1428  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1429  			PrevState: ValueState_NONEXISTENT,
  1430  			NewState:  ValueState_PENDING,
  1431  			NOOP:      true,
  1432  		},
  1433  	}
  1434  	checkTxnOperations(txn.Planned, txnOps)
  1435  	checkTxnOperations(txn.Executed, txnOps)
  1436  
  1437  	// check flag stats
  1438  	graphR := scheduler.graph.Read()
  1439  	errorStats := graphR.GetFlagStats(ErrorFlagIndex, nil)
  1440  	Expect(errorStats.TotalCount).To(BeEquivalentTo(0))
  1441  	pendingStats := graphR.GetFlagStats(UnavailValueFlagIndex, nil)
  1442  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(3))
  1443  	derivedStats := graphR.GetFlagStats(DerivedFlagIndex, nil)
  1444  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(0))
  1445  	lastUpdateStats := graphR.GetFlagStats(LastUpdateFlagIndex, nil)
  1446  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(3))
  1447  	descriptorStats := graphR.GetFlagStats(DescriptorFlagIndex, nil)
  1448  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(3))
  1449  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
  1450  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(3))
  1451  	valueStateStats := graphR.GetFlagStats(ValueStateFlagIndex, nil)
  1452  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(3))
  1453  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
  1454  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(3))
  1455  	graphR.Release()
  1456  
  1457  	// run second transaction that will make the cycle of values ready to be added
  1458  	startTime = time.Now()
  1459  	schedulerTxn = scheduler.StartNBTransaction()
  1460  	schedulerTxn.SetValue(prefixA+baseValue4, test.NewStringValue("base-value4-data"))
  1461  	seqNum, err = schedulerTxn.Commit(testCtx)
  1462  	stopTime = time.Now()
  1463  	Expect(seqNum).To(BeEquivalentTo(1))
  1464  	Expect(err).ShouldNot(HaveOccurred())
  1465  
  1466  	// check the state of SB
  1467  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
  1468  	Expect(mockSB.GetValues(nil)).To(HaveLen(4))
  1469  	// -> base value 1
  1470  	value := mockSB.GetValue(prefixA + baseValue1)
  1471  	Expect(value).ToNot(BeNil())
  1472  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value1-data"))).To(BeTrue())
  1473  	Expect(value.Metadata).To(BeNil())
  1474  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1475  	// -> base value 2
  1476  	value = mockSB.GetValue(prefixA + baseValue2)
  1477  	Expect(value).ToNot(BeNil())
  1478  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value2-data"))).To(BeTrue())
  1479  	Expect(value.Metadata).To(BeNil())
  1480  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1481  	// -> base value 3
  1482  	value = mockSB.GetValue(prefixA + baseValue3)
  1483  	Expect(value).ToNot(BeNil())
  1484  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value3-data"))).To(BeTrue())
  1485  	Expect(value.Metadata).To(BeNil())
  1486  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1487  	// -> base value 4
  1488  	value = mockSB.GetValue(prefixA + baseValue4)
  1489  	Expect(value).ToNot(BeNil())
  1490  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value4-data"))).To(BeTrue())
  1491  	Expect(value.Metadata).To(BeNil())
  1492  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1493  
  1494  	// check value states
  1495  	status = scheduler.GetValueStatus(prefixA + baseValue1)
  1496  	Expect(status).ToNot(BeNil())
  1497  	checkBaseValueStatus(status, &BaseValueStatus{
  1498  		Value: &ValueStatus{
  1499  			Key:           prefixA + baseValue1,
  1500  			State:         ValueState_CONFIGURED,
  1501  			LastOperation: TxnOperation_CREATE,
  1502  		},
  1503  	})
  1504  	status = scheduler.GetValueStatus(prefixA + baseValue2)
  1505  	Expect(status).ToNot(BeNil())
  1506  	checkBaseValueStatus(status, &BaseValueStatus{
  1507  		Value: &ValueStatus{
  1508  			Key:           prefixA + baseValue2,
  1509  			State:         ValueState_CONFIGURED,
  1510  			LastOperation: TxnOperation_CREATE,
  1511  		},
  1512  	})
  1513  	status = scheduler.GetValueStatus(prefixA + baseValue3)
  1514  	Expect(status).ToNot(BeNil())
  1515  	checkBaseValueStatus(status, &BaseValueStatus{
  1516  		Value: &ValueStatus{
  1517  			Key:           prefixA + baseValue3,
  1518  			State:         ValueState_CONFIGURED,
  1519  			LastOperation: TxnOperation_CREATE,
  1520  		},
  1521  	})
  1522  	status = scheduler.GetValueStatus(prefixA + baseValue4)
  1523  	Expect(status).ToNot(BeNil())
  1524  	checkBaseValueStatus(status, &BaseValueStatus{
  1525  		Value: &ValueStatus{
  1526  			Key:           prefixA + baseValue4,
  1527  			State:         ValueState_CONFIGURED,
  1528  			LastOperation: TxnOperation_CREATE,
  1529  		},
  1530  	})
  1531  
  1532  	// check operations executed in SB
  1533  	opHistory = mockSB.PopHistoryOfOps()
  1534  	Expect(opHistory).To(HaveLen(4))
  1535  	operation := opHistory[0]
  1536  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1537  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1538  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue4))
  1539  	Expect(operation.Err).To(BeNil())
  1540  	operation = opHistory[1]
  1541  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1542  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1543  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue3))
  1544  	Expect(operation.Err).To(BeNil())
  1545  	operation = opHistory[2]
  1546  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1547  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1548  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue2))
  1549  	Expect(operation.Err).To(BeNil())
  1550  	operation = opHistory[3]
  1551  	Expect(operation.OpType).To(Equal(test.MockCreate))
  1552  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1553  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
  1554  	Expect(operation.Err).To(BeNil())
  1555  
  1556  	// check transaction operations
  1557  	txnHistory = scheduler.GetTransactionHistory(time.Time{}, time.Now())
  1558  	Expect(txnHistory).To(HaveLen(2))
  1559  	txn = txnHistory[1]
  1560  	Expect(txn.PreRecord).To(BeFalse())
  1561  	Expect(txn.Start.After(startTime)).To(BeTrue())
  1562  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  1563  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  1564  	Expect(txn.SeqNum).To(BeEquivalentTo(1))
  1565  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  1566  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  1567  	Expect(txn.Description).To(BeEmpty())
  1568  	checkRecordedValues(txn.Values, []RecordedKVPair{
  1569  		{Key: prefixA + baseValue4, Value: utils.RecordProtoMessage(test.NewStringValue("base-value4-data")), Origin: FromNB},
  1570  	})
  1571  
  1572  	txnOps = RecordedTxnOps{
  1573  		{
  1574  			Operation: TxnOperation_CREATE,
  1575  			Key:       prefixA + baseValue4,
  1576  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value4-data")),
  1577  			PrevState: ValueState_NONEXISTENT,
  1578  			NewState:  ValueState_CONFIGURED,
  1579  		},
  1580  		{
  1581  			Operation: TxnOperation_CREATE,
  1582  			Key:       prefixA + baseValue3,
  1583  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1584  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1585  			PrevState: ValueState_PENDING,
  1586  			NewState:  ValueState_CONFIGURED,
  1587  		},
  1588  		{
  1589  			Operation: TxnOperation_CREATE,
  1590  			Key:       prefixA + baseValue2,
  1591  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value2-data")),
  1592  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value2-data")),
  1593  			PrevState: ValueState_PENDING,
  1594  			NewState:  ValueState_CONFIGURED,
  1595  		},
  1596  		{
  1597  			Operation: TxnOperation_CREATE,
  1598  			Key:       prefixA + baseValue1,
  1599  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1600  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1601  			PrevState: ValueState_PENDING,
  1602  			NewState:  ValueState_CONFIGURED,
  1603  		},
  1604  	}
  1605  	checkTxnOperations(txn.Planned, txnOps)
  1606  	checkTxnOperations(txn.Executed, txnOps)
  1607  
  1608  	// check flag stats
  1609  	graphR = scheduler.graph.Read()
  1610  	errorStats = graphR.GetFlagStats(ErrorFlagIndex, nil)
  1611  	Expect(errorStats.TotalCount).To(BeEquivalentTo(0))
  1612  	pendingStats = graphR.GetFlagStats(UnavailValueFlagIndex, nil)
  1613  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(3))
  1614  	derivedStats = graphR.GetFlagStats(DerivedFlagIndex, nil)
  1615  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(0))
  1616  	lastUpdateStats = graphR.GetFlagStats(LastUpdateFlagIndex, nil)
  1617  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(7))
  1618  	descriptorStats = graphR.GetFlagStats(DescriptorFlagIndex, nil)
  1619  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(7))
  1620  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
  1621  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(7))
  1622  	valueStateStats = graphR.GetFlagStats(ValueStateFlagIndex, nil)
  1623  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(7))
  1624  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
  1625  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(3))
  1626  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_CONFIGURED.String()))
  1627  	Expect(valueStateStats.PerValueCount[ValueState_CONFIGURED.String()]).To(BeEquivalentTo(4))
  1628  	graphR.Release()
  1629  
  1630  	// plan error before 3rd txn
  1631  	mockSB.PlanError(prefixA+baseValue2, errors.New("failed to remove the value"), nil)
  1632  
  1633  	// run third transaction that will break the cycle even though the delete operation will fail
  1634  	startTime = time.Now()
  1635  	schedulerTxn = scheduler.StartNBTransaction()
  1636  	schedulerTxn.SetValue(prefixA+baseValue2, nil)
  1637  	seqNum, err = schedulerTxn.Commit(testCtx)
  1638  	stopTime = time.Now()
  1639  	Expect(seqNum).To(BeEquivalentTo(2))
  1640  	Expect(err).ToNot(BeNil())
  1641  	txnErr := err.(*TransactionError)
  1642  	Expect(txnErr.GetTxnInitError()).ShouldNot(HaveOccurred())
  1643  	kvErrors := txnErr.GetKVErrors()
  1644  	Expect(kvErrors).To(HaveLen(1))
  1645  	Expect(kvErrors[0].Key).To(BeEquivalentTo(prefixA + baseValue2))
  1646  	Expect(kvErrors[0].TxnOperation).To(BeEquivalentTo(TxnOperation_DELETE))
  1647  	Expect(kvErrors[0].Error.Error()).To(BeEquivalentTo("failed to remove the value"))
  1648  
  1649  	// check the state of SB
  1650  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
  1651  	Expect(mockSB.GetValues(nil)).To(HaveLen(2))
  1652  	// -> base value 1 - pending
  1653  	value = mockSB.GetValue(prefixA + baseValue1)
  1654  	Expect(value).To(BeNil())
  1655  	// -> base value 2 - failed to remove
  1656  	value = mockSB.GetValue(prefixA + baseValue2)
  1657  	Expect(value).ToNot(BeNil())
  1658  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value2-data"))).To(BeTrue())
  1659  	Expect(value.Metadata).To(BeNil())
  1660  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1661  	// -> base value 3 - pending
  1662  	value = mockSB.GetValue(prefixA + baseValue3)
  1663  	Expect(value).To(BeNil())
  1664  	// -> base value 4
  1665  	value = mockSB.GetValue(prefixA + baseValue4)
  1666  	Expect(value).ToNot(BeNil())
  1667  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value4-data"))).To(BeTrue())
  1668  	Expect(value.Metadata).To(BeNil())
  1669  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1670  
  1671  	// check value states
  1672  	status = scheduler.GetValueStatus(prefixA + baseValue1)
  1673  	Expect(status).ToNot(BeNil())
  1674  	checkBaseValueStatus(status, &BaseValueStatus{
  1675  		Value: &ValueStatus{
  1676  			Key:           prefixA + baseValue1,
  1677  			State:         ValueState_PENDING,
  1678  			LastOperation: TxnOperation_DELETE,
  1679  			Details:       []string{prefixA + baseValue2},
  1680  		},
  1681  	})
  1682  	status = scheduler.GetValueStatus(prefixA + baseValue2)
  1683  	Expect(status).ToNot(BeNil())
  1684  	checkBaseValueStatus(status, &BaseValueStatus{
  1685  		Value: &ValueStatus{
  1686  			Key:           prefixA + baseValue2,
  1687  			State:         ValueState_FAILED,
  1688  			LastOperation: TxnOperation_DELETE,
  1689  			Error:         "failed to remove the value",
  1690  		},
  1691  	})
  1692  	status = scheduler.GetValueStatus(prefixA + baseValue3)
  1693  	Expect(status).ToNot(BeNil())
  1694  	checkBaseValueStatus(status, &BaseValueStatus{
  1695  		Value: &ValueStatus{
  1696  			Key:           prefixA + baseValue3,
  1697  			State:         ValueState_PENDING,
  1698  			LastOperation: TxnOperation_DELETE,
  1699  			Details:       []string{prefixA + baseValue1},
  1700  		},
  1701  	})
  1702  	status = scheduler.GetValueStatus(prefixA + baseValue4)
  1703  	Expect(status).ToNot(BeNil())
  1704  	checkBaseValueStatus(status, &BaseValueStatus{
  1705  		Value: &ValueStatus{
  1706  			Key:           prefixA + baseValue4,
  1707  			State:         ValueState_CONFIGURED,
  1708  			LastOperation: TxnOperation_CREATE,
  1709  		},
  1710  	})
  1711  
  1712  	// check operations executed in SB
  1713  	opHistory = mockSB.PopHistoryOfOps()
  1714  	Expect(opHistory).To(HaveLen(3))
  1715  	operation = opHistory[0]
  1716  	Expect(operation.OpType).To(Equal(test.MockDelete))
  1717  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1718  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue3))
  1719  	Expect(operation.Err).To(BeNil())
  1720  	operation = opHistory[1]
  1721  	Expect(operation.OpType).To(Equal(test.MockDelete))
  1722  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1723  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue1))
  1724  	Expect(operation.Err).To(BeNil())
  1725  	operation = opHistory[2]
  1726  	Expect(operation.OpType).To(Equal(test.MockDelete))
  1727  	Expect(operation.Descriptor).To(BeEquivalentTo(descriptor1Name))
  1728  	Expect(operation.Key).To(BeEquivalentTo(prefixA + baseValue2))
  1729  	Expect(operation.Err.Error()).To(BeEquivalentTo("failed to remove the value"))
  1730  
  1731  	// check transaction operations
  1732  	txnHistory = scheduler.GetTransactionHistory(time.Time{}, time.Now())
  1733  	Expect(txnHistory).To(HaveLen(3))
  1734  	txn = txnHistory[2]
  1735  	Expect(txn.PreRecord).To(BeFalse())
  1736  	Expect(txn.Start.After(startTime)).To(BeTrue())
  1737  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  1738  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  1739  	Expect(txn.SeqNum).To(BeEquivalentTo(2))
  1740  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  1741  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  1742  	Expect(txn.Description).To(BeEmpty())
  1743  	checkRecordedValues(txn.Values, []RecordedKVPair{
  1744  		{Key: prefixA + baseValue2, Value: utils.RecordProtoMessage(nil), Origin: FromNB},
  1745  	})
  1746  
  1747  	// -> planned
  1748  	txnOps = RecordedTxnOps{
  1749  		{
  1750  			Operation: TxnOperation_DELETE,
  1751  			Key:       prefixA + baseValue3,
  1752  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1753  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1754  			PrevState: ValueState_CONFIGURED,
  1755  			NewState:  ValueState_PENDING,
  1756  		},
  1757  		{
  1758  			Operation: TxnOperation_DELETE,
  1759  			Key:       prefixA + baseValue1,
  1760  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1761  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1762  			PrevState: ValueState_CONFIGURED,
  1763  			NewState:  ValueState_PENDING,
  1764  		},
  1765  		{
  1766  			Operation: TxnOperation_DELETE,
  1767  			Key:       prefixA + baseValue2,
  1768  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value2-data")),
  1769  			PrevState: ValueState_CONFIGURED,
  1770  			NewState:  ValueState_REMOVED,
  1771  		},
  1772  	}
  1773  	checkTxnOperations(txn.Planned, txnOps)
  1774  
  1775  	// -> executed
  1776  	txnOps = RecordedTxnOps{
  1777  		{
  1778  			Operation: TxnOperation_DELETE,
  1779  			Key:       prefixA + baseValue3,
  1780  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1781  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value3-data")),
  1782  			PrevState: ValueState_CONFIGURED,
  1783  			NewState:  ValueState_PENDING,
  1784  		},
  1785  		{
  1786  			Operation: TxnOperation_DELETE,
  1787  			Key:       prefixA + baseValue1,
  1788  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1789  			NewValue:  utils.RecordProtoMessage(test.NewStringValue("base-value1-data")),
  1790  			PrevState: ValueState_CONFIGURED,
  1791  			NewState:  ValueState_PENDING,
  1792  		},
  1793  		{
  1794  			Operation: TxnOperation_DELETE,
  1795  			Key:       prefixA + baseValue2,
  1796  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("base-value2-data")),
  1797  			PrevState: ValueState_CONFIGURED,
  1798  			NewState:  ValueState_FAILED,
  1799  			NewErr:    errors.New("failed to remove the value"),
  1800  		},
  1801  	}
  1802  	checkTxnOperations(txn.Executed, txnOps)
  1803  
  1804  	// check flag stats
  1805  	graphR = scheduler.graph.Read()
  1806  	errorStats = graphR.GetFlagStats(ErrorFlagIndex, nil)
  1807  	Expect(errorStats.TotalCount).To(BeEquivalentTo(1))
  1808  	pendingStats = graphR.GetFlagStats(UnavailValueFlagIndex, nil)
  1809  	Expect(pendingStats.TotalCount).To(BeEquivalentTo(6))
  1810  	derivedStats = graphR.GetFlagStats(DerivedFlagIndex, nil)
  1811  	Expect(derivedStats.TotalCount).To(BeEquivalentTo(0))
  1812  	lastUpdateStats = graphR.GetFlagStats(LastUpdateFlagIndex, nil)
  1813  	Expect(lastUpdateStats.TotalCount).To(BeEquivalentTo(10))
  1814  	descriptorStats = graphR.GetFlagStats(DescriptorFlagIndex, nil)
  1815  	Expect(descriptorStats.TotalCount).To(BeEquivalentTo(10))
  1816  	Expect(descriptorStats.PerValueCount).To(HaveKey(descriptor1Name))
  1817  	Expect(descriptorStats.PerValueCount[descriptor1Name]).To(BeEquivalentTo(10))
  1818  	valueStateStats = graphR.GetFlagStats(ValueStateFlagIndex, nil)
  1819  	Expect(valueStateStats.TotalCount).To(BeEquivalentTo(10))
  1820  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_PENDING.String()))
  1821  	Expect(valueStateStats.PerValueCount[ValueState_PENDING.String()]).To(BeEquivalentTo(5))
  1822  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_CONFIGURED.String()))
  1823  	Expect(valueStateStats.PerValueCount[ValueState_CONFIGURED.String()]).To(BeEquivalentTo(4))
  1824  	Expect(valueStateStats.PerValueCount).To(HaveKey(ValueState_FAILED.String()))
  1825  	Expect(valueStateStats.PerValueCount[ValueState_FAILED.String()]).To(BeEquivalentTo(1))
  1826  	graphR.Release()
  1827  
  1828  	// finally, run 4th txn to get back the removed value
  1829  	schedulerTxn = scheduler.StartNBTransaction()
  1830  	schedulerTxn.SetValue(prefixA+baseValue2, test.NewStringValue("base-value2-data-new"))
  1831  	seqNum, err = schedulerTxn.Commit(testCtx)
  1832  	Expect(seqNum).To(BeEquivalentTo(3))
  1833  	Expect(err).ShouldNot(HaveOccurred())
  1834  
  1835  	// check the state of SB
  1836  	//Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty()) <- there is a validation error, but that's OK since descriptor does not define Retrieve
  1837  	Expect(mockSB.GetValues(nil)).To(HaveLen(4))
  1838  	// -> base value 1
  1839  	value = mockSB.GetValue(prefixA + baseValue1)
  1840  	Expect(value).ToNot(BeNil())
  1841  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value1-data"))).To(BeTrue())
  1842  	Expect(value.Metadata).To(BeNil())
  1843  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1844  	// -> base value 2
  1845  	value = mockSB.GetValue(prefixA + baseValue2)
  1846  	Expect(value).ToNot(BeNil())
  1847  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value2-data-new"))).To(BeTrue())
  1848  	Expect(value.Metadata).To(BeNil())
  1849  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1850  	// -> base value 3
  1851  	value = mockSB.GetValue(prefixA + baseValue3)
  1852  	Expect(value).ToNot(BeNil())
  1853  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value3-data"))).To(BeTrue())
  1854  	Expect(value.Metadata).To(BeNil())
  1855  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1856  	// -> base value 4
  1857  	value = mockSB.GetValue(prefixA + baseValue4)
  1858  	Expect(value).ToNot(BeNil())
  1859  	Expect(proto.Equal(value.Value, test.NewStringValue("base-value4-data"))).To(BeTrue())
  1860  	Expect(value.Metadata).To(BeNil())
  1861  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1862  
  1863  	// check value states
  1864  	status = scheduler.GetValueStatus(prefixA + baseValue1)
  1865  	Expect(status).ToNot(BeNil())
  1866  	checkBaseValueStatus(status, &BaseValueStatus{
  1867  		Value: &ValueStatus{
  1868  			Key:           prefixA + baseValue1,
  1869  			State:         ValueState_CONFIGURED,
  1870  			LastOperation: TxnOperation_CREATE,
  1871  		},
  1872  	})
  1873  	status = scheduler.GetValueStatus(prefixA + baseValue2)
  1874  	Expect(status).ToNot(BeNil())
  1875  	checkBaseValueStatus(status, &BaseValueStatus{
  1876  		Value: &ValueStatus{
  1877  			Key:           prefixA + baseValue2,
  1878  			State:         ValueState_CONFIGURED,
  1879  			LastOperation: TxnOperation_CREATE,
  1880  		},
  1881  	})
  1882  	status = scheduler.GetValueStatus(prefixA + baseValue3)
  1883  	Expect(status).ToNot(BeNil())
  1884  	checkBaseValueStatus(status, &BaseValueStatus{
  1885  		Value: &ValueStatus{
  1886  			Key:           prefixA + baseValue3,
  1887  			State:         ValueState_CONFIGURED,
  1888  			LastOperation: TxnOperation_CREATE,
  1889  		},
  1890  	})
  1891  	status = scheduler.GetValueStatus(prefixA + baseValue4)
  1892  	Expect(status).ToNot(BeNil())
  1893  	checkBaseValueStatus(status, &BaseValueStatus{
  1894  		Value: &ValueStatus{
  1895  			Key:           prefixA + baseValue4,
  1896  			State:         ValueState_CONFIGURED,
  1897  			LastOperation: TxnOperation_CREATE,
  1898  		},
  1899  	})
  1900  }
  1901  
  1902  func TestFailedDeleteOfDerivedValue(t *testing.T) {
  1903  	RegisterTestingT(t)
  1904  
  1905  	// prepare KV Scheduler
  1906  	scheduler := NewPlugin(UseDeps(func(deps *Deps) {
  1907  		deps.HTTPHandlers = nil
  1908  	}))
  1909  	err := scheduler.Init()
  1910  	Expect(err).To(BeNil())
  1911  
  1912  	// prepare mocks
  1913  	mockSB := test.NewMockSouthbound()
  1914  	// descriptor:
  1915  	descriptor := test.NewMockDescriptor(&KVDescriptor{
  1916  		Name:          descriptor1Name,
  1917  		NBKeyPrefix:   prefixA,
  1918  		KeySelector:   prefixSelector(prefixA),
  1919  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
  1920  		DerivedValues: test.ArrayValueDerBuilder,
  1921  		WithMetadata:  true,
  1922  	}, mockSB, 0)
  1923  	scheduler.RegisterKVDescriptor(descriptor)
  1924  
  1925  	// run non-resync transaction against empty SB
  1926  	schedulerTxn := scheduler.StartNBTransaction()
  1927  	schedulerTxn.SetValue(prefixA+baseValue1, test.NewArrayValue("item1"))
  1928  	seqNum, err := schedulerTxn.Commit(testCtx)
  1929  	Expect(seqNum).To(BeEquivalentTo(0))
  1930  	Expect(err).ShouldNot(HaveOccurred())
  1931  
  1932  	// check the state of SB
  1933  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
  1934  	// -> base value 1
  1935  	value := mockSB.GetValue(prefixA + baseValue1)
  1936  	Expect(value).ToNot(BeNil())
  1937  	Expect(proto.Equal(value.Value, test.NewArrayValue("item1"))).To(BeTrue())
  1938  	Expect(value.Metadata).ToNot(BeNil())
  1939  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
  1940  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1941  	// -> item1 derived from base value 1
  1942  	value = mockSB.GetValue(prefixA + baseValue1 + "/item1")
  1943  	Expect(value).ToNot(BeNil())
  1944  	Expect(proto.Equal(value.Value, test.NewStringValue("item1"))).To(BeTrue())
  1945  	Expect(value.Metadata).To(BeNil())
  1946  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  1947  
  1948  	// plan error before 2nd txn
  1949  	failedDeleteClb := func() {
  1950  		mockSB.SetValue(prefixA+baseValue1, test.NewArrayValue("item1"),
  1951  			&test.OnlyInteger{Integer: 0}, FromNB, false)
  1952  	}
  1953  	mockSB.PlanError(prefixA+baseValue1+"/item1", errors.New("failed to delete value"), failedDeleteClb)
  1954  
  1955  	// run 2nd non-resync transaction that will have errors
  1956  	startTime := time.Now()
  1957  	schedulerTxn2 := scheduler.StartNBTransaction()
  1958  	schedulerTxn2.SetValue(prefixA+baseValue1, nil)
  1959  	seqNum, err = schedulerTxn2.Commit(testCtx)
  1960  	stopTime := time.Now()
  1961  	Expect(seqNum).To(BeEquivalentTo(1))
  1962  	Expect(err).ToNot(BeNil())
  1963  	txnErr := err.(*TransactionError)
  1964  	Expect(txnErr.GetTxnInitError()).ShouldNot(HaveOccurred())
  1965  	kvErrors := txnErr.GetKVErrors()
  1966  	Expect(kvErrors).To(HaveLen(1))
  1967  	Expect(kvErrors[0].Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item1"))
  1968  	Expect(kvErrors[0].TxnOperation).To(BeEquivalentTo(TxnOperation_DELETE))
  1969  	Expect(kvErrors[0].Error.Error()).To(BeEquivalentTo("failed to delete value"))
  1970  
  1971  	// check transaction operations
  1972  	txnHistory := scheduler.GetTransactionHistory(time.Time{}, time.Now())
  1973  	Expect(txnHistory).To(HaveLen(2))
  1974  	txn := txnHistory[1]
  1975  	Expect(txn.PreRecord).To(BeFalse())
  1976  	Expect(txn.Start.After(startTime)).To(BeTrue())
  1977  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  1978  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  1979  	Expect(txn.SeqNum).To(BeEquivalentTo(1))
  1980  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  1981  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  1982  	Expect(txn.Description).To(BeEmpty())
  1983  	checkRecordedValues(txn.Values, []RecordedKVPair{
  1984  		{Key: prefixA + baseValue1, Value: nil, Origin: FromNB},
  1985  	})
  1986  
  1987  	// -> planned
  1988  	txnOps := RecordedTxnOps{
  1989  		{
  1990  			Operation: TxnOperation_DELETE,
  1991  			Key:       prefixA + baseValue1 + "/item1",
  1992  			IsDerived: true,
  1993  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item1")),
  1994  			PrevState: ValueState_CONFIGURED,
  1995  			NewState:  ValueState_REMOVED,
  1996  		},
  1997  		{
  1998  			Operation: TxnOperation_DELETE,
  1999  			Key:       prefixA + baseValue1,
  2000  			PrevValue: utils.RecordProtoMessage(test.NewArrayValue("item1")),
  2001  			PrevState: ValueState_CONFIGURED,
  2002  			NewState:  ValueState_REMOVED,
  2003  		},
  2004  	}
  2005  	checkTxnOperations(txn.Planned, txnOps)
  2006  
  2007  	// -> executed
  2008  	txnOps = RecordedTxnOps{
  2009  		{
  2010  			Operation: TxnOperation_DELETE,
  2011  			Key:       prefixA + baseValue1 + "/item1",
  2012  			IsDerived: true,
  2013  			PrevValue: utils.RecordProtoMessage(test.NewStringValue("item1")),
  2014  			PrevState: ValueState_CONFIGURED,
  2015  			NewState:  ValueState_FAILED,
  2016  			NewErr:    errors.New("failed to delete value"),
  2017  		},
  2018  	}
  2019  	checkTxnOperations(txn.Executed, txnOps)
  2020  
  2021  	// check value status
  2022  	status := scheduler.GetValueStatus(prefixA + baseValue1)
  2023  	Expect(status).ToNot(BeNil())
  2024  	checkBaseValueStatus(status, &BaseValueStatus{
  2025  		Value: &ValueStatus{
  2026  			Key:           prefixA + baseValue1,
  2027  			State:         ValueState_CONFIGURED,
  2028  			LastOperation: TxnOperation_DELETE,
  2029  		},
  2030  		DerivedValues: []*ValueStatus{
  2031  			{
  2032  				Key:           prefixA + baseValue1 + "/item1",
  2033  				State:         ValueState_FAILED,
  2034  				LastOperation: TxnOperation_DELETE,
  2035  				Error:         "failed to delete value",
  2036  			},
  2037  		},
  2038  	})
  2039  
  2040  	// close scheduler
  2041  	err = scheduler.Close()
  2042  	Expect(err).To(BeNil())
  2043  }
  2044  
  2045  func TestFailedRecreateOfDerivedValue(t *testing.T) {
  2046  	RegisterTestingT(t)
  2047  
  2048  	// prepare KV Scheduler
  2049  	scheduler := NewPlugin(UseDeps(func(deps *Deps) {
  2050  		deps.HTTPHandlers = nil
  2051  	}))
  2052  	err := scheduler.Init()
  2053  	Expect(err).To(BeNil())
  2054  
  2055  	// prepare mocks
  2056  	mockSB := test.NewMockSouthbound()
  2057  	// descriptor:
  2058  	descriptor := test.NewMockDescriptor(&KVDescriptor{
  2059  		Name:          descriptor1Name,
  2060  		NBKeyPrefix:   prefixA,
  2061  		KeySelector:   prefixSelector(prefixA),
  2062  		ValueTypeName: string(proto.MessageName(test.NewArrayValue())),
  2063  		DerivedValues: test.ArrayValueDerBuilder,
  2064  		WithMetadata:  true,
  2065  		UpdateWithRecreate: func(key string, oldValue, newValue proto.Message, metadata Metadata) bool {
  2066  			return key == prefixA+baseValue1+"/item1"
  2067  		},
  2068  	}, mockSB, 0)
  2069  	scheduler.RegisterKVDescriptor(descriptor)
  2070  
  2071  	// run non-resync transaction against empty SB
  2072  	arrayVal1 := test.NewArrayValueWithSuffix("-v1", "item1")
  2073  	schedulerTxn := scheduler.StartNBTransaction()
  2074  	schedulerTxn.SetValue(prefixA+baseValue1, arrayVal1)
  2075  	seqNum, err := schedulerTxn.Commit(testCtx)
  2076  	Expect(seqNum).To(BeEquivalentTo(0))
  2077  	Expect(err).ShouldNot(HaveOccurred())
  2078  
  2079  	// check the state of SB
  2080  	Expect(mockSB.GetKeysWithInvalidData()).To(BeEmpty())
  2081  	// -> base value 1
  2082  	value := mockSB.GetValue(prefixA + baseValue1)
  2083  	Expect(value).ToNot(BeNil())
  2084  	Expect(proto.Equal(value.Value, arrayVal1)).To(BeTrue())
  2085  	Expect(value.Metadata).ToNot(BeNil())
  2086  	Expect(value.Metadata.(test.MetaWithInteger).GetInteger()).To(BeEquivalentTo(0))
  2087  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  2088  	// -> item1 derived from base value 1
  2089  	value = mockSB.GetValue(prefixA + baseValue1 + "/item1")
  2090  	Expect(value).ToNot(BeNil())
  2091  	Expect(proto.Equal(value.Value, test.NewStringValue("item1-v1"))).To(BeTrue())
  2092  	Expect(value.Metadata).To(BeNil())
  2093  	Expect(value.Origin).To(BeEquivalentTo(FromNB))
  2094  
  2095  	// plan error before 2nd txn
  2096  	failedCreateClb := func() {
  2097  		mockSB.SetValue(prefixA+baseValue1, test.NewArrayValue(),
  2098  			&test.OnlyInteger{Integer: 0}, FromNB, false)
  2099  	}
  2100  	mockSB.PlanError(prefixA+baseValue1+"/item1", nil, nil)                                              // Delete
  2101  	mockSB.PlanError(prefixA+baseValue1+"/item1", errors.New("failed to create value"), failedCreateClb) // (Re)Create
  2102  
  2103  	// run 2nd non-resync transaction that will have errors
  2104  	startTime := time.Now()
  2105  	schedulerTxn2 := scheduler.StartNBTransaction()
  2106  	arrayVal2 := test.NewArrayValueWithSuffix("-v2", "item1")
  2107  	schedulerTxn2.SetValue(prefixA+baseValue1, arrayVal2)
  2108  	seqNum, err = schedulerTxn2.Commit(testCtx)
  2109  	stopTime := time.Now()
  2110  	Expect(seqNum).To(BeEquivalentTo(1))
  2111  	Expect(err).ToNot(BeNil())
  2112  	txnErr := err.(*TransactionError)
  2113  	Expect(txnErr.GetTxnInitError()).ShouldNot(HaveOccurred())
  2114  	kvErrors := txnErr.GetKVErrors()
  2115  	Expect(kvErrors).To(HaveLen(1))
  2116  	Expect(kvErrors[0].Key).To(BeEquivalentTo(prefixA + baseValue1 + "/item1"))
  2117  	Expect(kvErrors[0].TxnOperation).To(BeEquivalentTo(TxnOperation_CREATE))
  2118  	Expect(kvErrors[0].Error.Error()).To(BeEquivalentTo("failed to create value"))
  2119  
  2120  	// check transaction operations
  2121  	txnHistory := scheduler.GetTransactionHistory(time.Time{}, time.Now())
  2122  	Expect(txnHistory).To(HaveLen(2))
  2123  	txn := txnHistory[1]
  2124  	Expect(txn.PreRecord).To(BeFalse())
  2125  	Expect(txn.Start.After(startTime)).To(BeTrue())
  2126  	Expect(txn.Start.Before(txn.Stop)).To(BeTrue())
  2127  	Expect(txn.Stop.Before(stopTime)).To(BeTrue())
  2128  	Expect(txn.SeqNum).To(BeEquivalentTo(1))
  2129  	Expect(txn.TxnType).To(BeEquivalentTo(NBTransaction))
  2130  	Expect(txn.ResyncType).To(BeEquivalentTo(NotResync))
  2131  	Expect(txn.Description).To(BeEmpty())
  2132  	checkRecordedValues(txn.Values, []RecordedKVPair{
  2133  		{Key: prefixA + baseValue1, Value: utils.RecordProtoMessage(arrayVal2), Origin: FromNB},
  2134  	})
  2135  
  2136  	// -> planned
  2137  	txnOps := RecordedTxnOps{
  2138  		{
  2139  			Operation: TxnOperation_UPDATE,
  2140  			Key:       prefixA + baseValue1,
  2141  			PrevValue: utils.RecordProtoMessage(arrayVal1),
  2142  			NewValue:  utils.RecordProtoMessage(arrayVal2),
  2143  			PrevState: ValueState_CONFIGURED,
  2144  			NewState:  ValueState_CONFIGURED,
  2145  		},
  2146  		{
  2147  			Operation:  TxnOperation_DELETE,
  2148  			Key:        prefixA + baseValue1 + "/item1",
  2149  			IsDerived:  true,
  2150  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1-v1")),
  2151  			PrevState:  ValueState_CONFIGURED,
  2152  			NewState:   ValueState_REMOVED,
  2153  			IsRecreate: true,
  2154  		},
  2155  		{
  2156  			Operation:  TxnOperation_CREATE,
  2157  			Key:        prefixA + baseValue1 + "/item1",
  2158  			IsDerived:  true,
  2159  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1-v2")),
  2160  			PrevState:  ValueState_REMOVED,
  2161  			NewState:   ValueState_CONFIGURED,
  2162  			IsRecreate: true,
  2163  		},
  2164  	}
  2165  	checkTxnOperations(txn.Planned, txnOps)
  2166  
  2167  	// -> executed
  2168  	txnOps = RecordedTxnOps{
  2169  		{
  2170  			Operation: TxnOperation_UPDATE,
  2171  			Key:       prefixA + baseValue1,
  2172  			PrevValue: utils.RecordProtoMessage(arrayVal1),
  2173  			NewValue:  utils.RecordProtoMessage(arrayVal2),
  2174  			PrevState: ValueState_CONFIGURED,
  2175  			NewState:  ValueState_CONFIGURED,
  2176  		},
  2177  		{
  2178  			Operation:  TxnOperation_DELETE,
  2179  			Key:        prefixA + baseValue1 + "/item1",
  2180  			IsDerived:  true,
  2181  			PrevValue:  utils.RecordProtoMessage(test.NewStringValue("item1-v1")),
  2182  			PrevState:  ValueState_CONFIGURED,
  2183  			NewState:   ValueState_REMOVED,
  2184  			IsRecreate: true,
  2185  		},
  2186  		{
  2187  			Operation:  TxnOperation_CREATE,
  2188  			Key:        prefixA + baseValue1 + "/item1",
  2189  			IsDerived:  true,
  2190  			NewValue:   utils.RecordProtoMessage(test.NewStringValue("item1-v2")),
  2191  			PrevState:  ValueState_REMOVED,
  2192  			NewState:   ValueState_FAILED,
  2193  			NewErr:     errors.New("failed to create value"),
  2194  			IsRecreate: true,
  2195  		},
  2196  	}
  2197  	checkTxnOperations(txn.Executed, txnOps)
  2198  
  2199  	// close scheduler
  2200  	err = scheduler.Close()
  2201  	Expect(err).To(BeNil())
  2202  }