Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions client/src/cbltest/api/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ class Listener:
"""A class representing the passive side of a replication inside of a test server"""

def __init__(
self, database: Database, collections: list[str], port: int | None = None
self,
database: Database,
collections: list[str],
port: int | None = None,
disable_tls: bool = False,
):
self.database = database
"""The database that the listener will be serving"""
Expand All @@ -34,6 +38,9 @@ def __init__(
value
"""

self.disable_tls = disable_tls
"""If True, TLS will be disabled for the listener"""

self.__original_port = port
self.__index = database._index
self.__request_factory = database._request_factory
Expand All @@ -48,7 +55,7 @@ async def start(self) -> None:
"""Start listening for incoming connections"""
with self.__tracer.start_as_current_span("start_listener"):
payload = PostStartListenerRequestBody(
self.database.name, self.collections, self.port
self.database.name, self.collections, self.port, self.disable_tls
)
request = self.__request_factory.create_request(
TestServerRequestType.START_LISTENER, payload
Expand Down
12 changes: 10 additions & 2 deletions client/src/cbltest/api/testserver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import cast
from urllib.parse import urljoin
from urllib.parse import urljoin, urlparse

from opentelemetry.trace import get_tracer

Expand Down Expand Up @@ -135,5 +135,13 @@ def replication_url(self, db_name: str, port: int):
ws_scheme = "ws://" # For now not using secure

_assert_not_null(db_name, "db_name")
replication_url = f"{ws_scheme}{self.url}:{port}"

parsed = urlparse(self.url)
if parsed.hostname:
hostname = parsed.hostname
elif parsed.netloc:
hostname = parsed.netloc.split(":")[0]
else:
hostname = self.url.split("://")[-1].split(":")[0]
replication_url = f"{ws_scheme}{hostname}:{port}"
return urljoin(replication_url, db_name)
17 changes: 16 additions & 1 deletion client/src/cbltest/v1/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,23 @@ def port(self) -> int | None:
"""The desired port to listen on (if None, the OS will choose)"""
return self.__port

def __init__(self, db: str, collections: list[str], port: int | None = None):
@property
def disableTLS(self) -> bool:
"""If True, TLS will be disabled for the listener"""
return self.__disable_tls

def __init__(
self,
db: str,
collections: list[str],
port: int | None = None,
disable_tls: bool = False,
):
super().__init__(1)
self.__database = db
self.__collections = collections
self.__port = port
self.__disable_tls = disable_tls

def to_json(self) -> Any:
json: dict[str, Any] = {
Expand All @@ -722,6 +734,9 @@ def to_json(self) -> Any:
if self.__port is not None:
json["port"] = self.__port

if self.__disable_tls:
json["disableTLS"] = self.__disable_tls
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be simply added without a change in the API spec in the specs folder for v1 (and implementation on ALL platforms: Java and Android are missing). What is the need for disabling TLS anyway?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback!

I didn’t realise the API spec needed to be updated first - I'll go ahead and add these changes there then?

I’ll also check the Java and Android parts and include the missing changes there.

About disabling TLS: these tests are for the P2P feature, and TLS isn’t a must for them. We can still have a few tests with TLS if needed, but it’s not really necessary for all cases.


return json


Expand Down
3 changes: 2 additions & 1 deletion servers/c/src/cbl/CBLManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ namespace ts::cbl {

/// URLEndpointListener

string CBLManager::startListener(const string &database, const vector<std::string>&collNames, int port) {
string CBLManager::startListener(const string &database, const vector<std::string>&collNames, int port, bool disableTLS) {
lock_guard <mutex> lock(_mutex);

CBLError error{};
Expand All @@ -562,6 +562,7 @@ namespace ts::cbl {
config.collections = collections.data();
config.collectionCount = collections.size();
config.port = port;
config.disableTLS = disableTLS;

auto listener = CBLURLEndpointListener_Create(&config, &error);
if (!listener) {
Expand Down
2 changes: 1 addition & 1 deletion servers/c/src/cbl/CBLManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ namespace ts::cbl {

/// Listener

std::string startListener(const std::string &database, const std::vector<std::string>&collections, int port);
std::string startListener(const std::string &database, const std::vector<std::string>&collections, int port, bool disableTLS = false);

CBLURLEndpointListener *listener(const std::string &id);

Expand Down
3 changes: 2 additions & 1 deletion servers/c/src/dispatcher/PostStartListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ int Dispatcher::handlePOSTStartListener(Request &request, Session *session) {
}

auto port = GetValue<int>(body, "port", 0);
auto disableTLS = GetValue<bool>(body, "disableTLS", false);

string id = session->cblManager()->startListener(database, collections, port);
string id = session->cblManager()->startListener(database, collections, port, disableTLS);

json result;
result["id"] = id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal readonly record struct StartListenerBody
public required string[] collections { get; init; }

public ushort port { get; init; }

public bool disableTLS { get; init; } = false;
}

[HttpHandler("startListener")]
Expand Down Expand Up @@ -48,7 +50,8 @@ public static Task StartListenerHandler(int version, Session session, JsonDocume

var listenerConfig = new URLEndpointListenerConfiguration(collectionObjects)
{
Port = deserializedBody.port
Port = deserializedBody.port,
DisableTLS = deserializedBody.disableTLS
};
(var listener, var id) = session.ObjectManager.RegisterObject(() => new URLEndpointListener(listenerConfig));
listener.Start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ extension ContentTypes {
let database: String
let collections: [String]
let port: UInt16?
let disableTLS: Bool?

public var description: String {
var result: String = "Endpoint Listener Configuration:\n"
result += "\tdatabase: \(database)\n"
result += "\tcollection: \(collections.joined(separator: ", "))\n"
result += "\tport: \(port ?? 0)\n"
result += "\tdisableTLS: \(disableTLS ?? false)\n"
return result
}
}
Expand Down
3 changes: 2 additions & 1 deletion servers/ios/TestServer/Handlers/StartListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ extension Handlers {
Log.log(level: .debug, message: "Starting Listener with config: \(listenerStartRq.description)")

let dbManager = req.databaseManager
let id = try dbManager.startListener(dbName: listenerStartRq.database, collections: listenerStartRq.collections, port: listenerStartRq.port)
let disableTLS = listenerStartRq.disableTLS ?? false
let id = try dbManager.startListener(dbName: listenerStartRq.database, collections: listenerStartRq.collections, port: listenerStartRq.port, disableTLS: disableTLS)

return ContentTypes.Listener(id: id, port: listenerStartRq.port)
}
Expand Down
3 changes: 2 additions & 1 deletion servers/ios/TestServer/Utils/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class DatabaseManager {
}
}

public func startListener(dbName: String, collections: [String], port: UInt16?) throws -> UUID {
public func startListener(dbName: String, collections: [String], port: UInt16?, disableTLS: Bool = false) throws -> UUID {
var collectionsArr: [Collection] = []

guard let database = databases[dbName]
Expand All @@ -105,6 +105,7 @@ class DatabaseManager {

var listenerConfig = URLEndpointListenerConfiguration(collections: collectionsArr)
listenerConfig.port = port
listenerConfig.disableTLS = disableTLS

let listener = URLEndpointListener(config: listenerConfig)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class EndptListenerManager {
private static final String KEY_DATABASE = "database";
private static final String KEY_COLLECTIONS = "collections";
private static final String KEY_PORT = "port";
private static final String KEY_DISABLE_TLS = "disableTLS";
private static final String KEY_ID = "id";

private static final Set<String> LEGAL_CREATE_KEYS;
Expand All @@ -54,6 +55,7 @@ public class EndptListenerManager {
l.add(KEY_DATABASE);
l.add(KEY_COLLECTIONS);
l.add(KEY_PORT);
l.add(KEY_DISABLE_TLS);
LEGAL_CREATE_KEYS = Collections.unmodifiableSet(l);
}

Expand Down Expand Up @@ -96,6 +98,9 @@ public Map<String, Object> startListener(@NonNull TestContext ctxt, @NonNull Typ
final Integer port = req.getInt(KEY_PORT);
if (port != null) { listenerConfig.setPort(port); }

final Boolean disableTLS = req.getBoolean(KEY_DISABLE_TLS);
if (disableTLS != null) { listenerConfig.setDisableTLS(disableTLS); }

final URLEndpointListener listener = new URLEndpointListener(listenerConfig);
try { listener.start(); }
catch (CouchbaseLiteException e) { throw new CblApiFailure("Failed to start listener", e); }
Expand Down
4 changes: 3 additions & 1 deletion spec/api/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ paths:
operationId: startListener
requestBody:
description: |-
The request object containing the collections to share and, optionally, the port to listen on.
The request object containing the collections to share and, optionally, the port to listen on and disableTLS flag.
content:
application/json:
schema:
Expand All @@ -791,6 +791,8 @@ paths:
port:
type: integer
example: 12345
disableTLS:
type: boolean
required: true
responses:
'200':
Expand Down
123 changes: 123 additions & 0 deletions spec/tests/QE/test_peer_to_peer_topology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Peer-to-Peer Topology Tests

This document describes the peer-to-peer topology tests for Couchbase Lite, testing legacy peer-to-peer functionality in different topology setups (mesh and loop) without Sync Gateway.

## test_peer_to_peer_topology_mesh

Test legacy peer-to-peer functionality with mesh topology setup where each peer replicates to all other peers in phases.

**Parameters:**
- `num_of_docs`: Number of documents to create (10 or 100)
- `continuous`: Whether replication is continuous (True or False)
- `replicator_type`: Type of replication (e.g., "push_pull")

<br>

**Steps:**
1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local database and load `empty` dataset on all devices.
3. **PHASE 1**: Peer 1 -> Peers [2, 3]
- Add `num_of_docs` documents to the database on peer 1:
* Documents named: `phase1-peer1-doc1`, `phase1-peer1-doc2`, ..., `phase1-peer1-doc{num_of_docs}`
* Each document contains a random integer value
- Start listeners on peers 2 and 3:
* database: `db1` (on each target peer)
* collections: `["_default._default"]`
* port: 59840
- Setup replicators from peer 1 to peers 2 and 3:
* endpoint: replication URL for each target peer's listener
* collections: `_default._default`
* type: `replicator_type` (e.g., "push_pull")
* continuous: `continuous`
- Start replication from peer 1 to peers 2 and 3.
- Wait for replication from peer 1 to complete:
* Target activity: IDLE if continuous, otherwise STOPPED
- Check that all device databases have the replicated documents after phase 1:
* All databases should have the same content
- Stop listeners after phase 1.
4. **PHASE 2**: Peer 2 -> Peers [1, 3]
- Add `num_of_docs` documents to the database on peer 2:
* Documents named: `phase2-peer2-doc1`, `phase2-peer2-doc2`, ..., `phase2-peer2-doc{num_of_docs}`
- Start listeners on peers 1 and 3:
* database: `db1` (on each target peer)
* collections: `["_default._default"]`
* port: 59840
- Setup replicators from peer 2 to peers 1 and 3.
- Start replication from peer 2 to peers 1 and 3.
- Wait for replication from peer 2 to complete.
- Check that all device databases have the replicated documents after phase 2.
- Stop listeners after phase 2.
5. **PHASE 3**: Peer 3 -> Peers [1, 2]
- Add `num_of_docs` documents to the database on peer 3:
* Documents named: `phase3-peer3-doc1`, `phase3-peer3-doc2`, ..., `phase3-peer3-doc{num_of_docs}`
- Start listeners on peers 1 and 2:
* database: `db1` (on each target peer)
* collections: `["_default._default"]`
* port: 59840
- Setup replicators from peer 3 to peers 1 and 2.
- Start replication from peer 3 to peers 1 and 2.
- Wait for replication from peer 3 to complete.
- Check that all device databases have the replicated documents after phase 3.
- Stop listeners after phase 3.

## test_peer_to_peer_topology_loop

Test legacy peer-to-peer functionality with loop topology setup where each peer replicates to the next peer in a circular chain.

**Parameters:**
- `num_of_docs`: Number of documents to create (10 or 100)
- `continuous`: Whether replication is continuous (True or False)
- `replicator_type`: Type of replication (e.g., "push_pull" or "pull")

<br>

**Steps:**
1. Verify CBL version >= 2.8.0 on all test servers.
2. Reset local database and load `empty` dataset on all devices.
3. **PHASE 1**: Peer 1 -> Peer 2
- Add `num_of_docs` documents to the database on peer 1:
* Documents named: `phase1-peer1-doc1`, `phase1-peer1-doc2`, ..., `phase1-peer1-doc{num_of_docs}`
* Each document contains a random integer value
- Start listener on peer 2:
* database: `db1` (on peer 2)
* collections: `["_default._default"]`
* port: 59840
- Setup replicator from peer 1 to peer 2:
* endpoint: replication URL for peer 2's listener
* collections: `_default._default`
* type: `replicator_type` (e.g., "push_pull" or "pull")
* continuous: `continuous`
- Start replication from peer 1 to peer 2.
- Wait for replication from peer 1 to peer 2 to complete:
* Target activity: IDLE if continuous, otherwise STOPPED
- Verify that peer 1 and peer 2 have the same content after phase 1:
* Source and target databases should match
- Stop listener after phase 1.
4. **PHASE 2**: Peer 2 -> Peer 3
- Add `num_of_docs` documents to the database on peer 2:
* Documents named: `phase2-peer2-doc1`, `phase2-peer2-doc2`, ..., `phase2-peer2-doc{num_of_docs}`
- Start listener on peer 3:
* database: `db1` (on peer 3)
* collections: `["_default._default"]`
* port: 59840
- Setup replicator from peer 2 to peer 3.
- Start replication from peer 2 to peer 3.
- Wait for replication from peer 2 to peer 3 to complete.
- Verify that peer 2 and peer 3 have the same content after phase 2.
- Stop listener after phase 2.
5. **PHASE 3**: Peer 3 -> Peer 1
- Add `num_of_docs` documents to the database on peer 3:
* Documents named: `phase3-peer3-doc1`, `phase3-peer3-doc2`, ..., `phase3-peer3-doc{num_of_docs}`
- Start listener on peer 1:
* database: `db1` (on peer 1)
* collections: `["_default._default"]`
* port: 59840
- Setup replicator from peer 3 to peer 1.
- Start replication from peer 3 to peer 1.
- Wait for replication from peer 3 to peer 1 to complete.
- Verify that peer 3 and peer 1 have the same content after phase 3.
- Stop listener after phase 3.
6. Verify all device databases have converged to the same content after all phases:
* All three peers should have identical content
* All documents from all phases should be present on all peers

Loading
Loading