Skip to content

Commit cac5e75

Browse files
authored
Implement 1.2.0 of the spec (#193)
Introduces multipeer replicator API (untested yet)
1 parent 9583e98 commit cac5e75

6 files changed

Lines changed: 432 additions & 25 deletions

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from typing import cast
2+
3+
from opentelemetry.trace import get_tracer
4+
5+
from cbltest.api.database import Database
6+
from cbltest.api.replicator import (
7+
ReplicatorCollectionEntry,
8+
)
9+
from cbltest.logging import cbl_error, cbl_trace
10+
from cbltest.requests import TestServerRequestType
11+
from cbltest.v1.requests import (
12+
PostGetMultipeerReplicatorStatusRequestBody,
13+
PostStartMultipeerReplicatorRequestBody,
14+
PostStopMultipeerReplicatorRequestBody,
15+
)
16+
from cbltest.v1.responses import (
17+
MultipeerReplicatorStatusEntry,
18+
PostGetMultipeerReplicatorStatusResponse,
19+
PostStartMultipeerReplicatorResponse,
20+
)
21+
from cbltest.version import VERSION
22+
23+
24+
class MultipeerReplicatorStatus:
25+
"""
26+
A class representing the status of a Couchbase Lite multipeer replicator
27+
"""
28+
29+
@property
30+
def replicators(self) -> list[MultipeerReplicatorStatusEntry]:
31+
"""Gets the list of replicators and their statuses"""
32+
return self.__replicators
33+
34+
def __init__(self, replicators: list[MultipeerReplicatorStatusEntry]):
35+
self.__replicators = replicators
36+
37+
38+
class MultipeerReplicator:
39+
"""
40+
A class representing a Couchbase Lite multipeer replicator inside a test server
41+
"""
42+
43+
@property
44+
def peerGroupID(self) -> str:
45+
"""Gets the peer group ID for the replicator"""
46+
return self.__peerGroupID
47+
48+
@property
49+
def database(self) -> Database:
50+
"""Gets the database for the replicator"""
51+
return self.__database
52+
53+
@property
54+
def collections(self) -> list[ReplicatorCollectionEntry]:
55+
"""Gets the collections for the replicator"""
56+
return self.__collections
57+
58+
def __init__(
59+
self,
60+
peerGroupID: str,
61+
database: Database,
62+
collections: list[ReplicatorCollectionEntry],
63+
):
64+
assert database._request_factory.version == 1, (
65+
"This version of the cbl test API requires request API v1"
66+
)
67+
self.__index = database._index
68+
self.__request_factory = database._request_factory
69+
self.__peerGroupID = peerGroupID
70+
self.__database = database
71+
assert len(collections) > 0, "At least one collection is required"
72+
self.__collections = collections
73+
self.__tracer = get_tracer(__name__, VERSION)
74+
self.__id: str = ""
75+
76+
async def start(self) -> None:
77+
"""
78+
Starts the multipeer replicator
79+
"""
80+
with self.__tracer.start_as_current_span("start_multipeer_replicator"):
81+
payload = PostStartMultipeerReplicatorRequestBody(
82+
self.__peerGroupID, self.__database.name, self.__collections
83+
)
84+
85+
req = self.__request_factory.create_request(
86+
TestServerRequestType.START_MULTIPEER_REPLICATOR, payload
87+
)
88+
resp = await self.__request_factory.send_request(self.__index, req)
89+
if resp.error is not None:
90+
cbl_error(
91+
"Failed to start multipeer replicator (see trace log for details)"
92+
)
93+
cbl_trace(resp.error.message)
94+
return None
95+
96+
cast_resp = cast(PostStartMultipeerReplicatorResponse, resp)
97+
self.__id = cast_resp.replicator_id
98+
99+
async def stop(self) -> None:
100+
"""
101+
Stops the multipeer replicator
102+
"""
103+
with self.__tracer.start_as_current_span("stop_multipeer_replicator"):
104+
if not self.__id:
105+
cbl_error("Cannot stop multipeer replicator, it has not been started")
106+
return None
107+
108+
req = self.__request_factory.create_request(
109+
TestServerRequestType.STOP_MULTIPEER_REPLICATOR,
110+
PostStopMultipeerReplicatorRequestBody(self.__id),
111+
)
112+
resp = await self.__request_factory.send_request(self.__index, req)
113+
if resp.error is not None:
114+
cbl_error(
115+
"Failed to stop multipeer replicator (see trace log for details)"
116+
)
117+
cbl_trace(resp.error.message)
118+
return
119+
120+
self.__id = ""
121+
122+
async def get_status(self) -> MultipeerReplicatorStatus | None:
123+
"""
124+
Gets the status of the multipeer replicator
125+
"""
126+
with self.__tracer.start_as_current_span("get_multipeer_replicator_status"):
127+
if not self.__id:
128+
cbl_error(
129+
"Cannot get status of multipeer replicator, it has not been started"
130+
)
131+
return None
132+
133+
req = self.__request_factory.create_request(
134+
TestServerRequestType.MULTIPEER_REPLICATOR_STATUS,
135+
PostGetMultipeerReplicatorStatusRequestBody(self.__id),
136+
)
137+
resp = await self.__request_factory.send_request(self.__index, req)
138+
if resp.error is not None:
139+
cbl_error(
140+
"Failed to get multipeer replicator status (see trace log for details)"
141+
)
142+
cbl_trace(resp.error.message)
143+
return None
144+
145+
cast_resp = cast(PostGetMultipeerReplicatorStatusResponse, resp)
146+
return MultipeerReplicatorStatus(cast_resp.replicators)

client/src/cbltest/requests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class TestServerRequestType(Enum):
3737
LOG = "PostLogRequest"
3838
START_LISTENER = "PostStartListenerRequest"
3939
STOP_LISTENER = "PostStopListenerRequest"
40+
START_MULTIPEER_REPLICATOR = "PostStartMultipeerReplicatorRequest"
41+
STOP_MULTIPEER_REPLICATOR = "PostStopMultipeerReplicatorRequest"
42+
MULTIPEER_REPLICATOR_STATUS = "PostGetMultipeerReplicatorStatusRequest"
4043

4144
def __str__(self) -> str:
4245
return self.value

client/src/cbltest/v1/requests.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,143 @@ def to_json(self) -> Any:
747747
return {"id": self.__id}
748748

749749

750+
class PostStartMultipeerReplicatorRequestBody(TestServerRequestBody):
751+
"""
752+
The body of a POST /startMultipeerReplicator request as specified in version 1 of the
753+
`spec <https://github.com/couchbaselabs/couchbase-lite-tests/blob/main/spec/api/api.yaml>`_
754+
755+
Example Body::
756+
757+
{
758+
"peerGroupID": "com.couchbase.testing",
759+
"database": "db1",
760+
"collections": [
761+
{
762+
"names": [
763+
"store.cloths",
764+
"store.shoes"
765+
],
766+
"channels": [
767+
"A",
768+
"B"
769+
],
770+
"documentIDs": [
771+
"doc1",
772+
"doc2"
773+
],
774+
"pushFilter": {
775+
"name": "documentIDs",
776+
"params": {
777+
"documentIDs": [
778+
"doc1",
779+
"doc2"
780+
]
781+
}
782+
},
783+
"pullFilter": {
784+
"name": "documentIDs",
785+
"params": {
786+
"documentIDs": [
787+
"doc1",
788+
"doc2"
789+
]
790+
}
791+
},
792+
"conflictResolver": {
793+
"name": "merge",
794+
"params": {
795+
"property": "region"
796+
}
797+
}
798+
}
799+
]
800+
}
801+
"""
802+
803+
@property
804+
def peerGroupID(self) -> str:
805+
"""Gets the peer group ID for the replicator"""
806+
return self.__peerGroupID
807+
808+
@property
809+
def database(self) -> str:
810+
"""Gets the database for the replicator"""
811+
return self.__database
812+
813+
@property
814+
def collections(self) -> list[ReplicatorCollectionEntry]:
815+
"""Gets the collections for the replicator"""
816+
return self.__collections
817+
818+
def __init__(
819+
self,
820+
peerGroupID: str,
821+
database: str,
822+
collections: list[ReplicatorCollectionEntry],
823+
):
824+
super().__init__(1)
825+
self.__peerGroupID = peerGroupID
826+
self.__database = database
827+
self.__collections = collections
828+
829+
def to_json(self) -> Any:
830+
return {
831+
"peerGroupID": self.__peerGroupID,
832+
"database": self.__database,
833+
"collections": self.__collections,
834+
}
835+
836+
837+
class PostStopMultipeerReplicatorRequestBody(TestServerRequestBody):
838+
"""
839+
The body of a POST /stopMultipeerReplicator request as specified in version 1 of the
840+
`spec <https://github.com/couchbaselabs/couchbase-lite-tests/blob/main/spec/api/api.yaml>`_
841+
842+
Example Body::
843+
844+
{
845+
"id": "123e4567-e89b-12d3-a456-426614174000"
846+
}
847+
"""
848+
849+
@property
850+
def id(self) -> str:
851+
"""The ID of the multipeer replicator to stop (returned from /startMultipeerReplicator)"""
852+
return self.__id
853+
854+
def __init__(self, id: str):
855+
super().__init__(1)
856+
self.__id = id
857+
858+
def to_json(self) -> Any:
859+
return {"id": self.__id}
860+
861+
862+
class PostGetMultipeerReplicatorStatusRequestBody(TestServerRequestBody):
863+
"""
864+
The body of a POST /getMultipeerReplicatorStatus request as specified in version 1 of the
865+
`spec <https://github.com/couchbaselabs/couchbase-lite-tests/blob/main/spec/api/api.yaml>`_
866+
867+
Example Body::
868+
869+
{
870+
"id": "123e4567-e89b-12d3-a456-426614174000"
871+
}
872+
"""
873+
874+
@property
875+
def id(self) -> str:
876+
"""The ID of the multipeer replicator to stop (returned from /startMultipeerReplicator)"""
877+
return self.__id
878+
879+
def __init__(self, id: str):
880+
super().__init__(1)
881+
self.__id = id
882+
883+
def to_json(self) -> Any:
884+
return {"id": self.__id}
885+
886+
750887
# Below this point are all of the concrete test server request types
751888
# Remember the note from the top of this file about the actual type of the payload
752889
class PostResetRequest(TestServerRequest):

0 commit comments

Comments
 (0)