Skip to content

Commit 301783d

Browse files
authored
CBG-4258 allow support of a CBL V3 client (#7721)
1 parent 588b1e0 commit 301783d

8 files changed

+152
-19
lines changed

topologytest/couchbase_lite_mock_peer_test.go

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type CouchbaseLiteMockPeer struct {
4343
blipClients map[string]*PeerBlipTesterClient
4444
name string
4545
symmetricRedundant bool // there is another peer that is symmetric to this one
46+
peerType PeerType
4647
}
4748

4849
func (p *CouchbaseLiteMockPeer) String() string {
@@ -98,7 +99,16 @@ func (p *CouchbaseLiteMockPeer) getSingleSGBlipClient() *PeerBlipTesterClient {
9899
// CreateDocument creates a document on the peer. The test will fail if the document already exists.
99100
func (p *CouchbaseLiteMockPeer) CreateDocument(dsName sgbucket.DataStoreName, docID string, body []byte) BodyAndVersion {
100101
client := p.getSingleSGBlipClient().CollectionClient(dsName)
101-
docVersion, hlv := client.AddHLVRev(docID, rest.EmptyDocVersion(), body)
102+
var docVersion db.DocVersion
103+
var hlv *db.HybridLogicalVector
104+
switch p.Type() {
105+
case PeerTypeCouchbaseLiteV3:
106+
docVersion = client.AddRev(docID, rest.EmptyDocVersion(), body)
107+
case PeerTypeCouchbaseLite:
108+
docVersion, hlv = client.AddHLVRev(docID, rest.EmptyDocVersion(), body)
109+
default:
110+
require.Fail(p.TB(), fmt.Sprintf("unsupported peer type %s for creating document", p.Type()))
111+
}
102112
docMetadata := DocMetadataFromDocVersion(p.TB(), docID, hlv, docVersion)
103113
p.TB().Logf("%s: Created document %s with %#v", p, docID, docMetadata)
104114
return BodyAndVersion{
@@ -114,9 +124,18 @@ func (p *CouchbaseLiteMockPeer) WriteDocument(dsName sgbucket.DataStoreName, doc
114124
_, parentMeta := p.getLatestDocVersion(dsName, docID)
115125
parentVersion := rest.EmptyDocVersion()
116126
if parentMeta != nil {
117-
parentVersion = &db.DocVersion{CV: parentMeta.CV(p.TB())}
127+
parentVersion = &db.DocVersion{CV: parentMeta.CV(p.TB()), RevTreeID: parentMeta.RevTreeID}
128+
}
129+
var docVersion db.DocVersion
130+
var hlv *db.HybridLogicalVector
131+
switch p.Type() {
132+
case PeerTypeCouchbaseLiteV3:
133+
docVersion = client.AddRev(docID, parentVersion, body)
134+
case PeerTypeCouchbaseLite:
135+
docVersion, hlv = client.AddHLVRev(docID, parentVersion, body)
136+
default:
137+
require.Fail(p.TB(), fmt.Sprintf("unsupported peer type %s for writing document", p.Type()))
118138
}
119-
docVersion, hlv := client.AddHLVRev(docID, parentVersion, body)
120139
docMetadata := DocMetadataFromDocVersion(p.TB(), docID, hlv, docVersion)
121140
p.TB().Logf("%s: Wrote document %s with %#+v", p, docID, docMetadata)
122141
return BodyAndVersion{
@@ -132,7 +151,7 @@ func (p *CouchbaseLiteMockPeer) DeleteDocument(dsName sgbucket.DataStoreName, do
132151
_, parentMeta := p.getLatestDocVersion(dsName, docID)
133152
parentVersion := rest.EmptyDocVersion()
134153
if parentMeta != nil {
135-
parentVersion = &db.DocVersion{CV: parentMeta.CV(p.TB())}
154+
parentVersion = &db.DocVersion{CV: parentMeta.CV(p.TB()), RevTreeID: parentMeta.RevTreeID}
136155
}
137156
docVersion, hlv := client.Delete(docID, parentVersion)
138157
docMeta := DocMetadataFromDocVersion(p.TB(), docID, hlv, docVersion)
@@ -142,14 +161,19 @@ func (p *CouchbaseLiteMockPeer) DeleteDocument(dsName sgbucket.DataStoreName, do
142161

143162
// WaitForDocVersion waits for a document to reach a specific version. The test will fail if the document does not reach the expected version in 20s.
144163
func (p *CouchbaseLiteMockPeer) WaitForDocVersion(dsName sgbucket.DataStoreName, docID string, expected DocMetadata, topology Topology) db.Body {
164+
compareHLV := !topology.CompareRevTreeOnly()
145165
var data []byte
146166
require.EventuallyWithT(p.TB(), func(c *assert.CollectT) {
147167
var actual *DocMetadata
148168
data, actual = p.getLatestDocVersion(dsName, docID)
149169
if !assert.NotNil(c, actual, "Could not find docID:%+v on %p\nVersion %#v", docID, p, expected) {
150170
return
151171
}
152-
assertHLVEqual(c, dsName, docID, p.name, *actual, data, expected, topology)
172+
if compareHLV {
173+
assertHLVEqual(c, dsName, docID, p.name, *actual, data, expected, topology)
174+
} else {
175+
assertRevTreeIDEqual(c, dsName, docID, p.name, *actual, data, expected, topology)
176+
}
153177
}, totalWaitTime, pollInterval)
154178
var body db.Body
155179
require.NoError(p.TB(), base.JSONUnmarshal(data, &body))
@@ -176,6 +200,9 @@ func (p *CouchbaseLiteMockPeer) WaitForCV(dsName sgbucket.DataStoreName, docID s
176200
func (p *CouchbaseLiteMockPeer) WaitForTombstoneVersion(dsName sgbucket.DataStoreName, docID string, expected DocMetadata, topology Topology) {
177201
client := p.getSingleSGBlipClient().CollectionClient(dsName)
178202
expectedVersion := db.DocVersion{CV: expected.CV(p.TB())}
203+
if p.Type() == PeerTypeCouchbaseLiteV3 {
204+
expectedVersion = db.DocVersion{RevTreeID: expected.RevTreeID}
205+
}
179206
require.EventuallyWithT(p.TB(), func(c *assert.CollectT) {
180207
isTombstone, err := client.IsVersionTombstone(docID, expectedVersion)
181208
require.NoError(c, err)
@@ -190,9 +217,10 @@ func (p *CouchbaseLiteMockPeer) Close() {
190217
}
191218
}
192219

193-
// Type returns PeerTypeCouchbaseLite.
220+
// Type returns PeerTypeCouchbaseLite if representing a Couchbase Lite 4.x client or PeerTypeCouchbaseLiteV3
221+
// if representing a 3.x client.
194222
func (p *CouchbaseLiteMockPeer) Type() PeerType {
195-
return PeerTypeCouchbaseLite
223+
return p.peerType
196224
}
197225

198226
// IsSymmetricRedundant returns true if there is another peer set up that is identical to this one, and this peer doesn't need to participate in unique actions.
@@ -226,8 +254,14 @@ func (p *CouchbaseLiteMockPeer) CreateReplication(peer Peer, config PeerReplicat
226254
}
227255
const username = "user"
228256
sg.rt.CreateUser(username, []string{"*"})
229-
replication.btcRunner.SetSubprotocols([]string{db.CBMobileReplicationV4.SubprotocolString()})
230-
// intentionally do not use base.TestCtx to drop test name for readability
257+
switch p.Type() {
258+
case PeerTypeCouchbaseLite:
259+
replication.btcRunner.SetSubprotocols([]string{db.CBMobileReplicationV4.SubprotocolString()})
260+
case PeerTypeCouchbaseLiteV3:
261+
replication.btcRunner.SetSubprotocols([]string{db.CBMobileReplicationV3.SubprotocolString()})
262+
default:
263+
require.Fail(p.TB(), fmt.Sprintf("unsupported peer type %v for pull replication", p.Type()))
264+
}
231265
ctx := base.CorrelationIDLogCtx(sg.rt.TB().Context(), p.name)
232266
replication.btc = replication.btcRunner.NewBlipTesterClientOptsWithRTAndContext(ctx, sg.rt, &rest.BlipTesterClientOpts{
233267
Username: "user",

topologytest/couchbase_server_peer_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,11 @@ func (p *CouchbaseServerPeer) waitForDocVersion(dsName sgbucket.DataStoreName, d
216216
return
217217
}
218218
version = getDocVersion(docID, p, cas, xattrs)
219-
assertHLVEqual(c, dsName, docID, p.name, version, docBytes, expected, topology)
219+
if expected.HasHLV() {
220+
assertHLVEqual(c, dsName, docID, p.name, version, docBytes, expected, topology)
221+
} else {
222+
assertRevTreeIDEqual(c, dsName, docID, p.name, version, docBytes, expected, topology)
223+
}
220224
}, totalWaitTime, pollInterval)
221225
return docBytes
222226
}

topologytest/hlv_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ func waitForConvergingTombstones(t *testing.T, dsName base.ScopeAndCollectionNam
172172
nonCBLVersion = &version
173173
continue
174174
}
175-
assertHLVEqual(c, dsName, docID, peer, version, nil, *nonCBLVersion, topology)
175+
if topology.CompareRevTreeOnly() {
176+
assertRevTreeIDEqual(c, dsName, docID, peer, version, nil, *nonCBLVersion, topology)
177+
} else {
178+
assertHLVEqual(c, dsName, docID, peer, version, nil, *nonCBLVersion, topology)
179+
}
176180
}
177181
}, totalWaitTime, pollInterval)
178182
}

topologytest/multi_actor_no_conflict_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ func TestMultiActorUpdate(t *testing.T) {
2424
t.Run(topologySpec.description, func(t *testing.T) {
2525
collectionName, topology := setupTests(t, topologySpec)
2626
topology.StartReplications()
27-
for createPeerName, createPeer := range topology.peers.ActivePeers() {
28-
for updatePeerName, updatePeer := range topology.SortedPeers() {
27+
for createPeerName, createPeer := range topology.ActivePeers() {
28+
for updatePeerName, updatePeer := range topology.ActivePeers() {
2929
topology.Run(t, "create="+createPeerName+",update="+updatePeerName, func(t *testing.T) {
3030
docID := getDocID(t) + "_create=" + createPeerName + ",update=" + updatePeerName
3131
body1 := []byte(fmt.Sprintf(`{"activePeer": "%s", "createPeer": "%s", "updatePeer": "%s", "topology": "%s", "action": "create"}`, createPeerName, createPeerName, updatePeer, topology.specDescription))
@@ -55,7 +55,7 @@ func TestMultiActorDelete(t *testing.T) {
5555
collectionName, topology := setupTests(t, topologySpec)
5656
topology.StartReplications()
5757
for createPeerName, createPeer := range topology.ActivePeers() {
58-
for deletePeerName, deletePeer := range topology.SortedPeers() {
58+
for deletePeerName, deletePeer := range topology.ActivePeers() {
5959
topology.Run(t, "create="+createPeerName+",delete="+deletePeerName, func(t *testing.T) {
6060
docID := getDocID(t) + "_create=" + createPeerName + ",update=" + deletePeerName
6161
body1 := []byte(fmt.Sprintf(`{"activePeer": "%s", "createPeer": "%s", "deletePeer": "%s", "topology": "%s", "action": "create"}`, createPeerName, createPeerName, deletePeer, topology.specDescription))
@@ -85,8 +85,8 @@ func TestMultiActorResurrect(t *testing.T) {
8585
collectionName, topology := setupTests(t, topologySpec)
8686
topology.StartReplications()
8787
for createPeerName, createPeer := range topology.ActivePeers() {
88-
for deletePeerName, deletePeer := range topology.SortedPeers() {
89-
for resurrectPeerName, resurrectPeer := range topology.SortedPeers() {
88+
for deletePeerName, deletePeer := range topology.ActivePeers() {
89+
for resurrectPeerName, resurrectPeer := range topology.ActivePeers() {
9090
topology.Run(t, fmt.Sprintf("create=%s,delete=%s,resurrect=%s", createPeerName, deletePeerName, resurrectPeerName), func(t *testing.T) {
9191
docID := getDocID(t) + "_create=" + createPeerName + ",delete=" + deletePeerName + ",resurrect=" + resurrectPeerName
9292
body1 := []byte(fmt.Sprintf(`{"activePeer": "%s", "createPeer": "%s", "deletePeer": "%s", "resurrectPeer": "%s", "topology": "%s", "action": "create"}`, createPeerName, createPeerName, deletePeer, resurrectPeer, topologySpec.description))

topologytest/peer_test.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ func (to Topology) GetDocState(t assert.TestingT, dsName sgbucket.DataStoreName,
233233

234234
// ActivePeers returns a list of unique peers in the topology. If there is an identical symmetric peer, do not return it.
235235
func (to Topology) ActivePeers() iter.Seq2[string, Peer] {
236+
if to.CompareRevTreeOnly() {
237+
return to.peers.NonImportSortedPeers()
238+
}
236239
return to.peers.ActivePeers()
237240
}
238241

@@ -241,6 +244,16 @@ func (to Topology) SortedPeers() iter.Seq2[string, Peer] {
241244
return to.peers.SortedPeers()
242245
}
243246

247+
// CompareRevTreeOnly is true for a given topology when comparing only revtree is correct.
248+
func (to Topology) CompareRevTreeOnly() bool {
249+
for _, peer := range to.peers.ActivePeers() {
250+
if peer.Type() == PeerTypeCouchbaseLiteV3 {
251+
return true
252+
}
253+
}
254+
return false
255+
}
256+
244257
// Run is equivalent to testing.T.Run() but updates the underlying TB to the new testing.T
245258
// so that checks are made against the right instance (otherwise the outer test complains
246259
// "subtest may have called FailNow on a parent test")
@@ -308,10 +321,27 @@ const (
308321
PeerTypeCouchbaseServer PeerType = iota
309322
// PeerTypeCouchbaseLite represents a Couchbase Lite peer. This is currently backed in memory but will be backed by in memory structure that will send and receive blip messages. Future expansion to real Couchbase Lite peer in CBG-4260.
310323
PeerTypeCouchbaseLite
324+
// PeerTypeCouchbaseLiteV3 represents a Couchbase Lite peer. This is currently backed in memory but will be backed by in memory structure that will send and receive blip messages. Future expansion to real Couchbase Lite peer in CBG-4260.
325+
PeerTypeCouchbaseLiteV3
311326
// PeerTypeSyncGateway represents a Sync Gateway peer backed by a RestTester.
312327
PeerTypeSyncGateway
313328
)
314329

330+
func (pt PeerType) String() string {
331+
switch pt {
332+
case PeerTypeCouchbaseServer:
333+
return "Couchbase Server Peer"
334+
case PeerTypeCouchbaseLite:
335+
return "Couchbase Lite Peer"
336+
case PeerTypeCouchbaseLiteV3:
337+
return "Couchbase Lite V3 Peer"
338+
case PeerTypeSyncGateway:
339+
return "Sync Gateway Peer"
340+
default:
341+
return fmt.Sprintf("Unknown Peer Type %d", pt)
342+
}
343+
}
344+
315345
// PeerBucketID represents a specific bucket for a test. This allows multiple Sync Gateway instances to point to the same bucket, or a different buckets. There is no significance to the numbering of the buckets. We can use as many buckets as the MainTestBucketPool allows.
316346
type PeerBucketID int
317347

@@ -349,14 +379,15 @@ func NewPeer(t *testing.T, name string, buckets map[PeerBucketID]*base.TestBucke
349379
}
350380
p.UpdateTB(t)
351381
return p
352-
case PeerTypeCouchbaseLite:
382+
case PeerTypeCouchbaseLite, PeerTypeCouchbaseLiteV3:
353383
require.Equal(t, PeerBucketNoBackingBucket, opts.BucketID, "bucket should not be specified for Couchbase Lite peer %+v", opts)
354384
_, ok := buckets[opts.BucketID]
355385
require.False(t, ok, "bucket should not be specified for Couchbase Lite peer")
356386
p := &CouchbaseLiteMockPeer{
357387
name: name,
358388
blipClients: make(map[string]*PeerBlipTesterClient),
359389
symmetricRedundant: opts.Symmetric,
390+
peerType: opts.Type,
360391
}
361392
p.UpdateTB(t)
362393
return p
@@ -365,7 +396,7 @@ func NewPeer(t *testing.T, name string, buckets map[PeerBucketID]*base.TestBucke
365396
require.True(t, ok, "bucket not found for bucket ID %d", opts.BucketID)
366397
return newSyncGatewayPeer(t, name, bucket, opts.Symmetric)
367398
default:
368-
require.Fail(t, fmt.Sprintf("unsupported peer type %T", opts.Type))
399+
require.Fail(t, fmt.Sprintf("unsupported peer type %s", opts.Type))
369400
}
370401
return nil
371402
}

topologytest/sync_gateway_peer_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ func (p *SyncGatewayPeer) WaitForDocVersion(dsName sgbucket.DataStoreName, docID
161161
// Only assert on CV since RevTreeID might not be present if this was a Couchbase Server write
162162
bodyBytes, err := doc.BodyBytes(ctx)
163163
assert.NoError(c, err)
164-
assertHLVEqual(c, dsName, docID, p.name, version, bodyBytes, expected, topology)
164+
if expected.HasHLV() {
165+
assertHLVEqual(c, dsName, docID, p.name, version, bodyBytes, expected, topology)
166+
} else {
167+
assertRevTreeIDEqual(c, dsName, docID, p.name, version, bodyBytes, expected, topology)
168+
}
165169
}, totalWaitTime, pollInterval)
166170
return doc.Body(ctx)
167171
}

topologytest/topologies_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,48 @@ var TopologySpecifications = []TopologySpecification{
5858
},
5959
},
6060
},
61+
{
62+
/*
63+
+ - - - - - - +
64+
' +---------+ '
65+
' | cbs1 | '
66+
' +---------+ '
67+
' +---------+ '
68+
' | sg1 | '
69+
' +---------+ '
70+
+ - - - - - - +
71+
^
72+
|
73+
|
74+
v
75+
+---------+
76+
|cbl1 (v3)|
77+
+---------+
78+
*/
79+
description: "CBLV3<->SG<->CBS 1.1",
80+
peers: map[string]PeerOptions{
81+
"cbs1": {Type: PeerTypeCouchbaseServer, BucketID: PeerBucketID1},
82+
"sg1": {Type: PeerTypeSyncGateway, BucketID: PeerBucketID1},
83+
"cbl1": {Type: PeerTypeCouchbaseLiteV3},
84+
},
85+
replications: []PeerReplicationDefinition{
86+
{
87+
activePeer: "cbl1",
88+
passivePeer: "sg1",
89+
config: PeerReplicationConfig{
90+
direction: PeerReplicationDirectionPull,
91+
},
92+
},
93+
{
94+
activePeer: "cbl1",
95+
passivePeer: "sg1",
96+
config: PeerReplicationConfig{
97+
direction: PeerReplicationDirectionPush,
98+
},
99+
},
100+
},
101+
},
102+
61103
{
62104
/*
63105
Test topology 1.2

topologytest/version_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ type DocMetadata struct {
2929
ImplicitHLV *db.HybridLogicalVector // ImplicitHLV is the version of the document, if there was no HLV
3030
}
3131

32+
// HasHLV returns true if the version has an HLV. This will always return true on Couchbase Server or Sync Gateway
33+
// peers since they can construct an implicit HLV from the CAS.
34+
func (v DocMetadata) HasHLV() bool {
35+
if v.HLV != nil && v.HLV.GetCurrentVersionString() != "" {
36+
return true
37+
}
38+
return v.ImplicitHLV != nil && v.ImplicitHLV.GetCurrentVersionString() != ""
39+
}
40+
3241
// CV returns the current version of the document.
3342
func (v DocMetadata) CV(t assert.TestingT) db.Version {
3443
if v.ImplicitHLV != nil {
@@ -98,3 +107,8 @@ func assertHLVEqual(t assert.TestingT, dsName sgbucket.DataStoreName, docID stri
98107
func assertCVEqual(t assert.TestingT, dsName sgbucket.DataStoreName, docID string, p string, version DocMetadata, body []byte, expected DocMetadata, topology Topology) {
99108
assert.Equal(t, expected.CV(t), version.CV(t), "Actual HLV's CV does not match expected on %s for peer %s. Expected: %#v, Actual: %#v\nActual Body: %s\n%s", docID, p, expected, version, body, topology.GetDocState(t, dsName, docID))
100109
}
110+
111+
// assertRevTreeIDEqual asserts that revtree ID of the version is equal to the expected CV.
112+
func assertRevTreeIDEqual(t assert.TestingT, dsName sgbucket.DataStoreName, docID string, p string, version DocMetadata, body []byte, expected DocMetadata, topology Topology) {
113+
assert.Equal(t, expected.RevTreeID, version.RevTreeID, "Actual revtree id does not match expected on %s for peer %s. Expected: %#v, Actual: %#v\nActual Body: %s\n%s", docID, p, expected, version, body, topology.GetDocState(t, dsName, docID))
114+
}

0 commit comments

Comments
 (0)