Skip to content

Commit ff00514

Browse files
Add integration and unit tests
1 parent 2b5e408 commit ff00514

File tree

7 files changed

+262
-8
lines changed

7 files changed

+262
-8
lines changed

Diff for: .github/workflows/integration-tests.yml

+7
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,11 @@ jobs:
3232
- name: Test with pytest
3333
run: |
3434
export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }}
35+
export SCYLLA_VERSION='release:5.1'
3536
./ci/run_integration_test.sh tests/integration/standard/ tests/integration/cqlengine/
37+
38+
- name: Test tablets
39+
run: |
40+
export EVENT_LOOP_MANAGER=${{ matrix.event_loop_manager }}
41+
export SCYLLA_VERSION='unstable/master:2024-01-03T08:06:57Z'
42+
./ci/run_integration_test.sh tests/integration/experiments/

Diff for: ci/run_integration_test.sh

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ if (( aio_max_nr != aio_max_nr_recommended_value )); then
1515
fi
1616
fi
1717

18-
SCYLLA_RELEASE='release:5.1'
19-
2018
python3 -m venv .test-venv
2119
source .test-venv/bin/activate
2220
pip install -U pip wheel setuptools
@@ -33,12 +31,11 @@ pip install https://github.com/scylladb/scylla-ccm/archive/master.zip
3331

3432
# download version
3533

36-
ccm create scylla-driver-temp -n 1 --scylla --version ${SCYLLA_RELEASE}
34+
ccm create scylla-driver-temp -n 1 --scylla --version ${SCYLLA_VERSION}
3735
ccm remove
3836

3937
# run test
4038

41-
export SCYLLA_VERSION=${SCYLLA_RELEASE}
4239
export MAPPED_SCYLLA_VERSION=3.11.4
4340
PROTOCOL_VERSION=4 pytest -rf --import-mode append $*
4441

Diff for: tests/integration/__init__.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ def _id_and_mark(f):
372372
# 1. unittest doesn't skip setUpClass when used on class and we need it sometimes
373373
# 2. unittest doesn't have conditional xfail, and I prefer to use pytest than custom decorator
374374
# 3. unittest doesn't have a reason argument, so you don't see the reason in pytest report
375-
requires_collection_indexes = pytest.mark.skipif(SCYLLA_VERSION is not None and Version(SCYLLA_VERSION.split(':')[1]) < Version('5.2'),
375+
# TODO remove second check when we stop using unstable version in CI for tablets
376+
requires_collection_indexes = pytest.mark.skipif(SCYLLA_VERSION is not None and (len(SCYLLA_VERSION.split('/')) != 0 or Version(SCYLLA_VERSION.split(':')[1]) < Version('5.2')),
376377
reason='Scylla supports collection indexes from 5.2 onwards')
377378
requires_custom_indexes = pytest.mark.skipif(SCYLLA_VERSION is not None,
378379
reason='Scylla does not support SASI or any other CUSTOM INDEX class')
@@ -501,7 +502,7 @@ def start_cluster_wait_for_up(cluster):
501502

502503

503504
def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None, set_keyspace=True, ccm_options=None,
504-
configuration_options=None, dse_options=None, use_single_interface=USE_SINGLE_INTERFACE):
505+
configuration_options=None, dse_options=None, use_single_interface=USE_SINGLE_INTERFACE, use_tablets=False):
505506
configuration_options = configuration_options or {}
506507
dse_options = dse_options or {}
507508
workloads = workloads or []
@@ -611,7 +612,10 @@ def use_cluster(cluster_name, nodes, ipformat=None, start=True, workloads=None,
611612
# CDC is causing an issue (can't start cluster with multiple seeds)
612613
# Selecting only features we need for tests, i.e. anything but CDC.
613614
CCM_CLUSTER = CCMScyllaCluster(path, cluster_name, **ccm_options)
614-
CCM_CLUSTER.set_configuration_options({'experimental_features': ['lwt', 'udf'], 'start_native_transport': True})
615+
if use_tablets:
616+
CCM_CLUSTER.set_configuration_options({'experimental_features': ['lwt', 'udf', 'consistent-topology-changes', 'tablets'], 'start_native_transport': True})
617+
else:
618+
CCM_CLUSTER.set_configuration_options({'experimental_features': ['lwt', 'udf'], 'start_native_transport': True})
615619

616620
# Permit IS NOT NULL restriction on non-primary key columns of a materialized view
617621
# This allows `test_metadata_with_quoted_identifiers` to run

Diff for: tests/integration/experiments/test_tablets.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import time
2+
import unittest
3+
import pytest
4+
import os
5+
from cassandra.cluster import Cluster
6+
from cassandra.policies import ConstantReconnectionPolicy, RoundRobinPolicy, TokenAwarePolicy
7+
8+
from tests.integration import PROTOCOL_VERSION, use_cluster
9+
from tests.unit.test_host_connection_pool import LOGGER
10+
11+
def setup_module():
12+
use_cluster('tablets', [3], start=True, use_tablets=True)
13+
14+
class TestTabletsIntegration(unittest.TestCase):
15+
@classmethod
16+
def setup_class(cls):
17+
cls.cluster = Cluster(contact_points=["127.0.0.1", "127.0.0.2", "127.0.0.3"], protocol_version=PROTOCOL_VERSION,
18+
load_balancing_policy=TokenAwarePolicy(RoundRobinPolicy()),
19+
reconnection_policy=ConstantReconnectionPolicy(1))
20+
cls.session = cls.cluster.connect()
21+
cls.create_ks_and_cf(cls)
22+
cls.create_data(cls.session)
23+
24+
@classmethod
25+
def teardown_class(cls):
26+
cls.cluster.shutdown()
27+
28+
def verify_same_host_in_tracing(self, results):
29+
traces = results.get_query_trace()
30+
events = traces.events
31+
host_set = set()
32+
for event in events:
33+
LOGGER.info("TRACE EVENT: %s %s %s", event.source, event.thread_name, event.description)
34+
host_set.add(event.source)
35+
36+
self.assertEqual(len(host_set), 1)
37+
self.assertIn('locally', "\n".join([event.description for event in events]))
38+
39+
trace_id = results.response_future.get_query_trace_ids()[0]
40+
traces = self.session.execute("SELECT * FROM system_traces.events WHERE session_id = %s", (trace_id,))
41+
events = [event for event in traces]
42+
host_set = set()
43+
for event in events:
44+
LOGGER.info("TRACE EVENT: %s %s", event.source, event.activity)
45+
host_set.add(event.source)
46+
47+
self.assertEqual(len(host_set), 1)
48+
self.assertIn('locally', "\n".join([event.activity for event in events]))
49+
50+
def verify_same_shard_in_tracing(self, results):
51+
traces = results.get_query_trace()
52+
events = traces.events
53+
shard_set = set()
54+
for event in events:
55+
LOGGER.info("TRACE EVENT: %s %s %s", event.source, event.thread_name, event.description)
56+
shard_set.add(event.thread_name)
57+
58+
self.assertEqual(len(shard_set), 1)
59+
self.assertIn('locally', "\n".join([event.description for event in events]))
60+
61+
trace_id = results.response_future.get_query_trace_ids()[0]
62+
traces = self.session.execute("SELECT * FROM system_traces.events WHERE session_id = %s", (trace_id,))
63+
events = [event for event in traces]
64+
shard_set = set()
65+
for event in events:
66+
LOGGER.info("TRACE EVENT: %s %s", event.thread, event.activity)
67+
shard_set.add(event.thread)
68+
69+
self.assertEqual(len(shard_set), 1)
70+
self.assertIn('locally', "\n".join([event.activity for event in events]))
71+
72+
def create_ks_and_cf(self):
73+
self.session.execute(
74+
"""
75+
DROP KEYSPACE IF EXISTS test1
76+
"""
77+
)
78+
self.session.execute(
79+
"""
80+
CREATE KEYSPACE test1
81+
WITH replication = {
82+
'class': 'NetworkTopologyStrategy',
83+
'replication_factor': 1,
84+
'initial_tablets': 8
85+
}
86+
""")
87+
88+
self.session.execute(
89+
"""
90+
CREATE TABLE test1.table1 (pk int, ck int, v int, PRIMARY KEY (pk, ck));
91+
""")
92+
93+
@staticmethod
94+
def create_data(session):
95+
prepared = session.prepare(
96+
"""
97+
INSERT INTO test1.table1 (pk, ck, v) VALUES (?, ?, ?)
98+
""")
99+
100+
for i in range(50):
101+
bound = prepared.bind((i, i%5, i%2))
102+
session.execute(bound)
103+
104+
def query_data_shard_select(self, session, verify_in_tracing=True):
105+
prepared = session.prepare(
106+
"""
107+
SELECT pk, ck, v FROM test1.table1 WHERE pk = ?
108+
""")
109+
110+
bound = prepared.bind([(2)])
111+
results = session.execute(bound, trace=True)
112+
self.assertEqual(results, [(2, 2, 0)])
113+
if verify_in_tracing:
114+
self.verify_same_shard_in_tracing(results)
115+
116+
def query_data_host_select(self, session, verify_in_tracing=True):
117+
prepared = session.prepare(
118+
"""
119+
SELECT pk, ck, v FROM test1.table1 WHERE pk = ?
120+
""")
121+
122+
bound = prepared.bind([(2)])
123+
results = session.execute(bound, trace=True)
124+
self.assertEqual(results, [(2, 2, 0)])
125+
if verify_in_tracing:
126+
self.verify_same_host_in_tracing(results)
127+
128+
def query_data_shard_insert(self, session, verify_in_tracing=True):
129+
prepared = session.prepare(
130+
"""
131+
INSERT INTO test1.table1 (pk, ck, v) VALUES (?, ?, ?)
132+
""")
133+
134+
bound = prepared.bind([(51), (1), (2)])
135+
results = session.execute(bound, trace=True)
136+
if verify_in_tracing:
137+
self.verify_same_shard_in_tracing(results)
138+
139+
def query_data_host_insert(self, session, verify_in_tracing=True):
140+
prepared = session.prepare(
141+
"""
142+
INSERT INTO test1.table1 (pk, ck, v) VALUES (?, ?, ?)
143+
""")
144+
145+
bound = prepared.bind([(52), (1), (2)])
146+
results = session.execute(bound, trace=True)
147+
if verify_in_tracing:
148+
self.verify_same_host_in_tracing(results)
149+
150+
def test_tablets(self):
151+
self.query_data_host_select(self.session)
152+
self.query_data_host_insert(self.session)
153+
154+
def test_tablets_shard_awareness(self):
155+
self.query_data_shard_select(self.session)
156+
self.query_data_shard_insert(self.session)

Diff for: tests/unit/test_policies.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from threading import Thread
2525

2626
from cassandra import ConsistencyLevel
27-
from cassandra.cluster import Cluster
27+
from cassandra.cluster import Cluster, ControlConnection
2828
from cassandra.metadata import Metadata
2929
from cassandra.policies import (RoundRobinPolicy, WhiteListRoundRobinPolicy, DCAwareRoundRobinPolicy,
3030
TokenAwarePolicy, SimpleConvictionPolicy,
@@ -601,6 +601,7 @@ def get_replicas(keyspace, packed_key):
601601
class FakeCluster:
602602
def __init__(self):
603603
self.metadata = Mock(spec=Metadata)
604+
self.control_connection = Mock(spec=ControlConnection)
604605

605606
def test_get_distance(self):
606607
"""

Diff for: tests/unit/test_response_future.py

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ResponseFutureTests(unittest.TestCase):
4040
def make_basic_session(self):
4141
s = Mock(spec=Session)
4242
s.row_factory = lambda col_names, rows: [(col_names, rows)]
43+
s.cluster.control_connection._tablets_routing_v1 = False
4344
return s
4445

4546
def make_pool(self):

Diff for: tests/unit/test_tablets.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import unittest
2+
3+
from cassandra.tablets import Tablets, Tablet
4+
5+
class TabletsTest(unittest.TestCase):
6+
def compare_ranges(self, tablets, ranges):
7+
self.assertEqual(len(tablets), len(ranges))
8+
9+
for idx, tablet in enumerate(tablets):
10+
self.assertEqual(tablet.first_token, ranges[idx][0], "First token is not correct in tablet: {}".format(tablet))
11+
self.assertEqual(tablet.last_token, ranges[idx][1], "Last token is not correct in tablet: {}".format(tablet))
12+
13+
def test_add_tablet_to_empty_tablets(self):
14+
tablets = Tablets({("test_ks", "test_tb"): []})
15+
16+
tablets.add_tablet("test_ks", "test_tb", Tablet(-6917529027641081857, -4611686018427387905, None))
17+
18+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
19+
20+
self.compare_ranges(tablets_list, [(-6917529027641081857, -4611686018427387905)])
21+
22+
def test_add_tablet_at_the_beggining(self):
23+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-6917529027641081857, -4611686018427387905, None)]})
24+
25+
tablets.add_tablet("test_ks", "test_tb", Tablet(-8611686018427387905, -7917529027641081857, None))
26+
27+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
28+
29+
self.compare_ranges(tablets_list, [(-8611686018427387905, -7917529027641081857),
30+
(-6917529027641081857, -4611686018427387905)])
31+
32+
def test_add_tablet_at_the_end(self):
33+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-6917529027641081857, -4611686018427387905, None)]})
34+
35+
tablets.add_tablet("test_ks", "test_tb", Tablet(-1, 2305843009213693951, None))
36+
37+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
38+
39+
self.compare_ranges(tablets_list, [(-6917529027641081857, -4611686018427387905),
40+
(-1, 2305843009213693951)])
41+
42+
def test_add_tablet_in_the_middle(self):
43+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-6917529027641081857, -4611686018427387905, None),
44+
Tablet(-1, 2305843009213693951, None)]},)
45+
46+
tablets.add_tablet("test_ks", "test_tb", Tablet(-4611686018427387905, -2305843009213693953, None))
47+
48+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
49+
50+
self.compare_ranges(tablets_list, [(-6917529027641081857, -4611686018427387905),
51+
(-4611686018427387905, -2305843009213693953),
52+
(-1, 2305843009213693951)])
53+
54+
def test_add_tablet_intersecting(self):
55+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-6917529027641081857, -4611686018427387905, None),
56+
Tablet(-4611686018427387905, -2305843009213693953, None),
57+
Tablet(-2305843009213693953, -1, None),
58+
Tablet(-1, 2305843009213693951, None)]})
59+
60+
tablets.add_tablet("test_ks", "test_tb", Tablet(-3611686018427387905, -6, None))
61+
62+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
63+
64+
self.compare_ranges(tablets_list, [(-6917529027641081857, -4611686018427387905),
65+
(-3611686018427387905, -6),
66+
(-1, 2305843009213693951)])
67+
68+
def test_add_tablet_intersecting_with_first(self):
69+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-8611686018427387905, -7917529027641081857, None),
70+
Tablet(-6917529027641081857, -4611686018427387905, None)]})
71+
72+
tablets.add_tablet("test_ks", "test_tb", Tablet(-8011686018427387905, -7987529027641081857, None))
73+
74+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
75+
76+
self.compare_ranges(tablets_list, [(-8011686018427387905, -7987529027641081857),
77+
(-6917529027641081857, -4611686018427387905)])
78+
79+
def test_add_tablet_intersecting_with_last(self):
80+
tablets = Tablets({("test_ks", "test_tb"): [Tablet(-8611686018427387905, -7917529027641081857, None),
81+
Tablet(-6917529027641081857, -4611686018427387905, None)]})
82+
83+
tablets.add_tablet("test_ks", "test_tb", Tablet(-5011686018427387905, -2987529027641081857, None))
84+
85+
tablets_list = tablets._tablets.get(("test_ks", "test_tb"))
86+
87+
self.compare_ranges(tablets_list, [(-8611686018427387905, -7917529027641081857),
88+
(-5011686018427387905, -2987529027641081857)])

0 commit comments

Comments
 (0)