Skip to content

Commit 1842c2e

Browse files
committed
PYTHON-1207 Support transient replication in metadata
1 parent 316937f commit 1842c2e

3 files changed

Lines changed: 125 additions & 12 deletions

File tree

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
======
33
Unreleased
44

5+
Features
6+
--------
7+
Transient Replication Support (PYTHON-1207)
8+
59
Bug Fixes
610
---------
711
* Asyncore logging exception on shutdown (PYTHON-1228)

cassandra/metadata.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -450,18 +450,37 @@ def make_token_replica_map(self, token_to_host_owner, ring):
450450
return {}
451451

452452

453+
def parse_replication_factor(input_rf):
454+
"""
455+
Given the inputted replication factor, returns a tuple containing number of total replicas
456+
and number of transient replicas
457+
"""
458+
transient_replicas = None
459+
try:
460+
total_replicas = int(input_rf)
461+
except ValueError:
462+
try:
463+
rf = input_rf.split('/')
464+
total_replicas, transient_replicas = int(rf[0]), int(rf[1])
465+
except Exception:
466+
raise ValueError("Unable to determine replication factor from: {}".format(input_rf))
467+
return total_replicas, transient_replicas
468+
469+
453470
class SimpleStrategy(ReplicationStrategy):
454471

455472
replication_factor = None
456473
"""
457474
The replication factor for this keyspace.
458475
"""
476+
transient_replicas = None
477+
"""
478+
The number of transient replicas for this keyspace.
479+
"""
459480

460481
def __init__(self, options_map):
461-
try:
462-
self.replication_factor = int(options_map['replication_factor'])
463-
except Exception:
464-
raise ValueError("SimpleStrategy requires an integer 'replication_factor' option")
482+
self._raw_replication_factor = options_map['replication_factor']
483+
self.replication_factor, self.transient_replicas = parse_replication_factor(self._raw_replication_factor)
465484

466485
def make_token_replica_map(self, token_to_host_owner, ring):
467486
replica_map = {}
@@ -482,14 +501,14 @@ def export_for_schema(self):
482501
Returns a string version of these replication options which are
483502
suitable for use in a CREATE KEYSPACE statement.
484503
"""
485-
return "{'class': 'SimpleStrategy', 'replication_factor': '%d'}" \
486-
% (self.replication_factor,)
504+
return "{'class': 'SimpleStrategy', 'replication_factor': '%s'}" \
505+
% (self._raw_replication_factor,)
487506

488507
def __eq__(self, other):
489508
if not isinstance(other, SimpleStrategy):
490509
return False
491510

492-
return self.replication_factor == other.replication_factor
511+
return str(self._raw_replication_factor) == str(other._raw_replication_factor)
493512

494513

495514
class NetworkTopologyStrategy(ReplicationStrategy):
@@ -500,12 +519,19 @@ class NetworkTopologyStrategy(ReplicationStrategy):
500519
"""
501520

502521
def __init__(self, dc_replication_factors):
503-
self.dc_replication_factors = dict(
504-
(str(k), int(v)) for k, v in dc_replication_factors.items())
522+
try:
523+
self.dc_replication_factors = dict(
524+
(str(k), int(v)) for k, v in dc_replication_factors.items())
525+
except ValueError:
526+
self.dc_replication_factors = dict(
527+
(str(k), str(v)) for k, v in dc_replication_factors.items())
505528

506529
def make_token_replica_map(self, token_to_host_owner, ring):
507-
dc_rf_map = dict((dc, int(rf))
508-
for dc, rf in self.dc_replication_factors.items() if rf > 0)
530+
dc_rf_map = {}
531+
for dc, rf in self.dc_replication_factors.items():
532+
total_rf = parse_replication_factor(rf)[0]
533+
if total_rf > 0:
534+
dc_rf_map[dc] = total_rf
509535

510536
# build a map of DCs to lists of indexes into `ring` for tokens that
511537
# belong to that DC
@@ -586,7 +612,7 @@ def export_for_schema(self):
586612
"""
587613
ret = "{'class': 'NetworkTopologyStrategy'"
588614
for dc, repl_factor in sorted(self.dc_replication_factors.items()):
589-
ret += ", '%s': '%d'" % (dc, repl_factor)
615+
ret += ", '%s': '%s'" % (dc, repl_factor)
590616
return ret + "}"
591617

592618
def __eq__(self, other):

tests/unit/test_metadata.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,89 @@ def test_replication_strategy(self):
8585
self.assertRaises(NotImplementedError, rs.make_token_replica_map, None, None)
8686
self.assertRaises(NotImplementedError, rs.export_for_schema)
8787

88+
def test_simple_replication_type_parsing(self):
89+
""" Test equality between passing numeric and string replication factor for simple strategy """
90+
rs = ReplicationStrategy()
91+
92+
simple_int = rs.create('SimpleStrategy', {'replication_factor': 3})
93+
simple_str = rs.create('SimpleStrategy', {'replication_factor': '3'})
94+
95+
self.assertEqual(simple_int.export_for_schema(), simple_str.export_for_schema())
96+
self.assertEqual(simple_int, simple_str)
97+
98+
# make token replica map
99+
ring = [MD5Token(0), MD5Token(1), MD5Token(2)]
100+
hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)]
101+
token_to_host = dict(zip(ring, hosts))
102+
self.assertEqual(
103+
simple_int.make_token_replica_map(token_to_host, ring),
104+
simple_str.make_token_replica_map(token_to_host, ring)
105+
)
106+
107+
def test_transient_replication_parsing(self):
108+
""" Test that we can PARSE a transient replication factor for SimpleStrategy """
109+
rs = ReplicationStrategy()
110+
111+
simple_transient = rs.create('SimpleStrategy', {'replication_factor': '3/1'})
112+
self.assertEqual(simple_transient.replication_factor, 3)
113+
self.assertEqual(simple_transient.transient_replicas, 1)
114+
self.assertIn("'replication_factor': '3/1'", simple_transient.export_for_schema())
115+
116+
simple_str = rs.create('SimpleStrategy', {'replication_factor': '3'})
117+
self.assertNotEqual(simple_transient, simple_str)
118+
119+
# make token replica map
120+
ring = [MD5Token(0), MD5Token(1), MD5Token(2)]
121+
hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)]
122+
token_to_host = dict(zip(ring, hosts))
123+
self.assertEqual(
124+
simple_transient.make_token_replica_map(token_to_host, ring),
125+
simple_str.make_token_replica_map(token_to_host, ring)
126+
)
127+
128+
def test_nts_replication_parsing(self):
129+
""" Test equality between passing numeric and string replication factor for NTS """
130+
rs = ReplicationStrategy()
131+
132+
nts_int = rs.create('NetworkTopologyStrategy', {'dc1': 3, 'dc2': 5})
133+
nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'})
134+
135+
self.assertEqual(nts_int.dc_replication_factors['dc1'], 3)
136+
self.assertEqual(nts_str.dc_replication_factors['dc1'], 3)
137+
138+
self.assertEqual(nts_int.export_for_schema(), nts_str.export_for_schema())
139+
self.assertEqual(nts_int, nts_str)
140+
141+
# make token replica map
142+
ring = [MD5Token(0), MD5Token(1), MD5Token(2)]
143+
hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)]
144+
token_to_host = dict(zip(ring, hosts))
145+
self.assertEqual(
146+
nts_int.make_token_replica_map(token_to_host, ring),
147+
nts_str.make_token_replica_map(token_to_host, ring)
148+
)
149+
150+
def test_nts_transient_parsing(self):
151+
""" Test that we can PARSE a transient replication factor for NTS """
152+
rs = ReplicationStrategy()
153+
154+
nts_transient = rs.create('NetworkTopologyStrategy', {'dc1': '3/1', 'dc2': '5/1'})
155+
self.assertEqual(nts_transient.dc_replication_factors['dc1'], '3/1')
156+
self.assertEqual(nts_transient.dc_replication_factors['dc2'], '5/1')
157+
self.assertIn("'dc1': '3/1', 'dc2': '5/1'", nts_transient.export_for_schema())
158+
159+
nts_str = rs.create('NetworkTopologyStrategy', {'dc1': '3', 'dc2': '5'})
160+
self.assertNotEqual(nts_transient, nts_str)
161+
162+
# make token replica map
163+
ring = [MD5Token(0), MD5Token(1), MD5Token(2)]
164+
hosts = [Host('dc1.{}'.format(host), SimpleConvictionPolicy) for host in range(3)]
165+
token_to_host = dict(zip(ring, hosts))
166+
self.assertEqual(
167+
nts_transient.make_token_replica_map(token_to_host, ring),
168+
nts_str.make_token_replica_map(token_to_host, ring)
169+
)
170+
88171
def test_nts_make_token_replica_map(self):
89172
token_to_host_owner = {}
90173

0 commit comments

Comments
 (0)