# Copyright 2013-2016 DataStax, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. try: import unittest2 as unittest except ImportError: import unittest # noqa from binascii import unhexlify from mock import Mock import os import six import timeit import cassandra from cassandra.marshal import uint16_unpack, uint16_pack from cassandra.metadata import (Murmur3Token, MD5Token, BytesToken, ReplicationStrategy, NetworkTopologyStrategy, SimpleStrategy, LocalStrategy, protect_name, protect_names, protect_value, is_valid_name, UserType, KeyspaceMetadata, get_schema_parser, _UnknownStrategy, ColumnMetadata, TableMetadata, IndexMetadata, Function, Aggregate, Metadata) from cassandra.policies import SimpleConvictionPolicy from cassandra.pool import Host class StrategiesTest(unittest.TestCase): @classmethod def setUpClass(cls): "Hook method for setting up class fixture before running tests in the class." if not hasattr(cls, 'assertItemsEqual'): cls.assertItemsEqual = cls.assertCountEqual def test_replication_strategy(self): """ Basic code coverage testing that ensures different ReplicationStrategies can be initiated using parameters correctly. """ rs = ReplicationStrategy() self.assertEqual(rs.create('OldNetworkTopologyStrategy', None), _UnknownStrategy('OldNetworkTopologyStrategy', None)) fake_options_map = {'options': 'map'} uks = rs.create('OldNetworkTopologyStrategy', fake_options_map) self.assertEqual(uks, _UnknownStrategy('OldNetworkTopologyStrategy', fake_options_map)) self.assertEqual(uks.make_token_replica_map({}, []), {}) fake_options_map = {'dc1': '3'} self.assertIsInstance(rs.create('NetworkTopologyStrategy', fake_options_map), NetworkTopologyStrategy) self.assertEqual(rs.create('NetworkTopologyStrategy', fake_options_map).dc_replication_factors, NetworkTopologyStrategy(fake_options_map).dc_replication_factors) fake_options_map = {'options': 'map'} self.assertIsNone(rs.create('SimpleStrategy', fake_options_map)) fake_options_map = {'options': 'map'} self.assertIsInstance(rs.create('LocalStrategy', fake_options_map), LocalStrategy) fake_options_map = {'options': 'map', 'replication_factor': 3} self.assertIsInstance(rs.create('SimpleStrategy', fake_options_map), SimpleStrategy) self.assertEqual(rs.create('SimpleStrategy', fake_options_map).replication_factor, SimpleStrategy(fake_options_map).replication_factor) self.assertEqual(rs.create('xxxxxxxx', fake_options_map), _UnknownStrategy('xxxxxxxx', fake_options_map)) self.assertRaises(NotImplementedError, rs.make_token_replica_map, None, None) self.assertRaises(NotImplementedError, rs.export_for_schema) def test_nts_make_token_replica_map(self): token_to_host_owner = {} dc1_1 = Host('dc1.1', SimpleConvictionPolicy) dc1_2 = Host('dc1.2', SimpleConvictionPolicy) dc1_3 = Host('dc1.3', SimpleConvictionPolicy) for host in (dc1_1, dc1_2, dc1_3): host.set_location_info('dc1', 'rack1') token_to_host_owner[MD5Token(0)] = dc1_1 token_to_host_owner[MD5Token(100)] = dc1_2 token_to_host_owner[MD5Token(200)] = dc1_3 dc2_1 = Host('dc2.1', SimpleConvictionPolicy) dc2_2 = Host('dc2.2', SimpleConvictionPolicy) dc2_1.set_location_info('dc2', 'rack1') dc2_2.set_location_info('dc2', 'rack1') token_to_host_owner[MD5Token(1)] = dc2_1 token_to_host_owner[MD5Token(101)] = dc2_2 dc3_1 = Host('dc3.1', SimpleConvictionPolicy) dc3_1.set_location_info('dc3', 'rack3') token_to_host_owner[MD5Token(2)] = dc3_1 ring = [MD5Token(0), MD5Token(1), MD5Token(2), MD5Token(100), MD5Token(101), MD5Token(200)] nts = NetworkTopologyStrategy({'dc1': 2, 'dc2': 2, 'dc3': 1}) replica_map = nts.make_token_replica_map(token_to_host_owner, ring) self.assertItemsEqual(replica_map[MD5Token(0)], (dc1_1, dc1_2, dc2_1, dc2_2, dc3_1)) def test_nts_token_performance(self): """ Tests to ensure that when rf exceeds the number of nodes available, that we dont' needlessly iterate trying to construct tokens for nodes that don't exist. @since 3.7 @jira_ticket PYTHON-379 @expected_result timing with 1500 rf should be same/similar to 3rf if we have 3 nodes @test_category metadata """ token_to_host_owner = {} ring = [] dc1hostnum = 3 current_token = 0 vnodes_per_host = 500 for i in range(dc1hostnum): host = Host('dc1.{0}'.format(i), SimpleConvictionPolicy) host.set_location_info('dc1', "rack1") for vnode_num in range(vnodes_per_host): md5_token = MD5Token(current_token+vnode_num) token_to_host_owner[md5_token] = host ring.append(md5_token) current_token += 1000 nts = NetworkTopologyStrategy({'dc1': 3}) start_time = timeit.default_timer() nts.make_token_replica_map(token_to_host_owner, ring) elapsed_base = timeit.default_timer() - start_time nts = NetworkTopologyStrategy({'dc1': 1500}) start_time = timeit.default_timer() nts.make_token_replica_map(token_to_host_owner, ring) elapsed_bad = timeit.default_timer() - start_time difference = elapsed_bad - elapsed_base self.assertTrue(difference < 1 and difference > -1) def test_nts_make_token_replica_map_multi_rack(self): token_to_host_owner = {} # (A) not enough distinct racks, first skipped is used dc1_1 = Host('dc1.1', SimpleConvictionPolicy) dc1_2 = Host('dc1.2', SimpleConvictionPolicy) dc1_3 = Host('dc1.3', SimpleConvictionPolicy) dc1_4 = Host('dc1.4', SimpleConvictionPolicy) dc1_1.set_location_info('dc1', 'rack1') dc1_2.set_location_info('dc1', 'rack1') dc1_3.set_location_info('dc1', 'rack2') dc1_4.set_location_info('dc1', 'rack2') token_to_host_owner[MD5Token(0)] = dc1_1 token_to_host_owner[MD5Token(100)] = dc1_2 token_to_host_owner[MD5Token(200)] = dc1_3 token_to_host_owner[MD5Token(300)] = dc1_4 # (B) distinct racks, but not contiguous dc2_1 = Host('dc2.1', SimpleConvictionPolicy) dc2_2 = Host('dc2.2', SimpleConvictionPolicy) dc2_3 = Host('dc2.3', SimpleConvictionPolicy) dc2_1.set_location_info('dc2', 'rack1') dc2_2.set_location_info('dc2', 'rack1') dc2_3.set_location_info('dc2', 'rack2') token_to_host_owner[MD5Token(1)] = dc2_1 token_to_host_owner[MD5Token(101)] = dc2_2 token_to_host_owner[MD5Token(201)] = dc2_3 ring = [MD5Token(0), MD5Token(1), MD5Token(100), MD5Token(101), MD5Token(200), MD5Token(201), MD5Token(300)] nts = NetworkTopologyStrategy({'dc1': 3, 'dc2': 2}) replica_map = nts.make_token_replica_map(token_to_host_owner, ring) token_replicas = replica_map[MD5Token(0)] self.assertItemsEqual(token_replicas, (dc1_1, dc1_2, dc1_3, dc2_1, dc2_3)) def test_nts_make_token_replica_map_empty_dc(self): host = Host('1', SimpleConvictionPolicy) host.set_location_info('dc1', 'rack1') token_to_host_owner = {MD5Token(0): host} ring = [MD5Token(0)] nts = NetworkTopologyStrategy({'dc1': 1, 'dc2': 0}) replica_map = nts.make_token_replica_map(token_to_host_owner, ring) self.assertEqual(set(replica_map[MD5Token(0)]), set([host])) def test_nts_export_for_schema(self): strategy = NetworkTopologyStrategy({'dc1': '1', 'dc2': '2'}) self.assertEqual("{'class': 'NetworkTopologyStrategy', 'dc1': '1', 'dc2': '2'}", strategy.export_for_schema()) def test_simple_strategy_make_token_replica_map(self): host1 = Host('1', SimpleConvictionPolicy) host2 = Host('2', SimpleConvictionPolicy) host3 = Host('3', SimpleConvictionPolicy) token_to_host_owner = { MD5Token(0): host1, MD5Token(100): host2, MD5Token(200): host3 } ring = [MD5Token(0), MD5Token(100), MD5Token(200)] rf1_replicas = SimpleStrategy({'replication_factor': '1'}).make_token_replica_map(token_to_host_owner, ring) self.assertItemsEqual(rf1_replicas[MD5Token(0)], [host1]) self.assertItemsEqual(rf1_replicas[MD5Token(100)], [host2]) self.assertItemsEqual(rf1_replicas[MD5Token(200)], [host3]) rf2_replicas = SimpleStrategy({'replication_factor': '2'}).make_token_replica_map(token_to_host_owner, ring) self.assertItemsEqual(rf2_replicas[MD5Token(0)], [host1, host2]) self.assertItemsEqual(rf2_replicas[MD5Token(100)], [host2, host3]) self.assertItemsEqual(rf2_replicas[MD5Token(200)], [host3, host1]) rf3_replicas = SimpleStrategy({'replication_factor': '3'}).make_token_replica_map(token_to_host_owner, ring) self.assertItemsEqual(rf3_replicas[MD5Token(0)], [host1, host2, host3]) self.assertItemsEqual(rf3_replicas[MD5Token(100)], [host2, host3, host1]) self.assertItemsEqual(rf3_replicas[MD5Token(200)], [host3, host1, host2]) def test_ss_equals(self): self.assertNotEqual(SimpleStrategy({'replication_factor': '1'}), NetworkTopologyStrategy({'dc1': 2})) class NameEscapingTest(unittest.TestCase): def test_protect_name(self): """ Test cassandra.metadata.protect_name output """ self.assertEqual(protect_name('tests'), 'tests') self.assertEqual(protect_name('test\'s'), '"test\'s"') self.assertEqual(protect_name('test\'s'), "\"test's\"") self.assertEqual(protect_name('tests ?!@#$%^&*()'), '"tests ?!@#$%^&*()"') self.assertEqual(protect_name('1'), '"1"') self.assertEqual(protect_name('1test'), '"1test"') def test_protect_names(self): """ Test cassandra.metadata.protect_names output """ self.assertEqual(protect_names(['tests']), ['tests']) self.assertEqual(protect_names( [ 'tests', 'test\'s', 'tests ?!@#$%^&*()', '1' ]), [ 'tests', "\"test's\"", '"tests ?!@#$%^&*()"', '"1"' ]) def test_protect_value(self): """ Test cassandra.metadata.protect_value output """ self.assertEqual(protect_value(True), "true") self.assertEqual(protect_value(False), "false") self.assertEqual(protect_value(3.14), '3.14') self.assertEqual(protect_value(3), '3') self.assertEqual(protect_value('test'), "'test'") self.assertEqual(protect_value('test\'s'), "'test''s'") self.assertEqual(protect_value(None), 'NULL') def test_is_valid_name(self): """ Test cassandra.metadata.is_valid_name output """ self.assertEqual(is_valid_name(None), False) self.assertEqual(is_valid_name('test'), True) self.assertEqual(is_valid_name('Test'), False) self.assertEqual(is_valid_name('t_____1'), True) self.assertEqual(is_valid_name('test1'), True) self.assertEqual(is_valid_name('1test1'), False) invalid_keywords = cassandra.metadata.cql_keywords - cassandra.metadata.cql_keywords_unreserved for keyword in invalid_keywords: self.assertEqual(is_valid_name(keyword), False) class Murmur3TokensTest(unittest.TestCase): def test_murmur3_init(self): murmur3_token = Murmur3Token(cassandra.metadata.MIN_LONG - 1) self.assertEqual(str(murmur3_token), '') def test_python_vs_c(self): from cassandra.murmur3 import _murmur3 as mm3_python try: from cassandra.cmurmur3 import murmur3 as mm3_c iterations = 100 for _ in range(iterations): for len in range(0, 32): # zero to one block plus full range of tail lengths key = os.urandom(len) self.assertEqual(mm3_python(key), mm3_c(key)) except ImportError: raise unittest.SkipTest('The cmurmur3 extension is not available') def test_murmur3_python(self): from cassandra.murmur3 import _murmur3 self._verify_hash(_murmur3) def test_murmur3_c(self): try: from cassandra.cmurmur3 import murmur3 self._verify_hash(murmur3) except ImportError: raise unittest.SkipTest('The cmurmur3 extension is not available') def _verify_hash(self, fn): self.assertEqual(fn(six.b('123')), -7468325962851647638) self.assertEqual(fn(b'\x00\xff\x10\xfa\x99' * 10), 5837342703291459765) self.assertEqual(fn(b'\xfe' * 8), -8927430733708461935) self.assertEqual(fn(b'\x10' * 8), 1446172840243228796) self.assertEqual(fn(six.b(str(cassandra.metadata.MAX_LONG))), 7162290910810015547) class MD5TokensTest(unittest.TestCase): def test_md5_tokens(self): md5_token = MD5Token(cassandra.metadata.MIN_LONG - 1) self.assertEqual(md5_token.hash_fn('123'), 42767516990368493138776584305024125808) self.assertEqual(md5_token.hash_fn(str(cassandra.metadata.MAX_LONG)), 28528976619278518853815276204542453639) self.assertEqual(str(md5_token), '' % -9223372036854775809) class BytesTokensTest(unittest.TestCase): def test_bytes_tokens(self): bytes_token = BytesToken(unhexlify(six.b('01'))) self.assertEqual(bytes_token.value, six.b('\x01')) self.assertEqual(str(bytes_token), "" % bytes_token.value) self.assertEqual(bytes_token.hash_fn('123'), '123') self.assertEqual(bytes_token.hash_fn(123), 123) self.assertEqual(bytes_token.hash_fn(str(cassandra.metadata.MAX_LONG)), str(cassandra.metadata.MAX_LONG)) def test_from_string(self): from_unicode = BytesToken.from_string(six.text_type('0123456789abcdef')) from_bin = BytesToken.from_string(six.b('0123456789abcdef')) self.assertEqual(from_unicode, from_bin) self.assertIsInstance(from_unicode.value, six.binary_type) self.assertIsInstance(from_bin.value, six.binary_type) def test_comparison(self): tok = BytesToken.from_string(six.text_type('0123456789abcdef')) token_high_order = uint16_unpack(tok.value[0:2]) self.assertLess(BytesToken(uint16_pack(token_high_order - 1)), tok) self.assertGreater(BytesToken(uint16_pack(token_high_order + 1)), tok) def test_comparison_unicode(self): value = six.b('\'_-()"\xc2\xac') t0 = BytesToken(value) t1 = BytesToken.from_string('00') self.assertGreater(t0, t1) self.assertFalse(t0 < t1) class KeyspaceMetadataTest(unittest.TestCase): def test_export_as_string_user_types(self): keyspace_name = 'test' keyspace = KeyspaceMetadata(keyspace_name, True, 'SimpleStrategy', dict(replication_factor=3)) keyspace.user_types['a'] = UserType(keyspace_name, 'a', ['one', 'two'], ['c', 'int']) keyspace.user_types['b'] = UserType(keyspace_name, 'b', ['one', 'two', 'three'], ['d', 'int', 'a']) keyspace.user_types['c'] = UserType(keyspace_name, 'c', ['one'], ['int']) keyspace.user_types['d'] = UserType(keyspace_name, 'd', ['one'], ['c']) self.assertEqual("""CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '3'} AND durable_writes = true; CREATE TYPE test.c ( one int ); CREATE TYPE test.a ( one c, two int ); CREATE TYPE test.d ( one c ); CREATE TYPE test.b ( one d, two int, three a );""", keyspace.export_as_string()) class UserTypesTest(unittest.TestCase): def test_as_cql_query(self): field_types = ['varint', 'ascii', 'frozen>'] udt = UserType("ks1", "mytype", ["a", "b", "c"], field_types) self.assertEqual("CREATE TYPE ks1.mytype (a varint, b ascii, c frozen>)", udt.as_cql_query(formatted=False)) self.assertEqual("""CREATE TYPE ks1.mytype ( a varint, b ascii, c frozen> );""", udt.export_as_string()) def test_as_cql_query_name_escaping(self): udt = UserType("MyKeyspace", "MyType", ["AbA", "keyspace"], ['ascii', 'ascii']) self.assertEqual('CREATE TYPE "MyKeyspace"."MyType" ("AbA" ascii, "keyspace" ascii)', udt.as_cql_query(formatted=False)) class IndexTest(unittest.TestCase): def test_build_index_as_cql(self): column_meta = Mock() column_meta.name = 'column_name_here' column_meta.table.name = 'table_name_here' column_meta.table.keyspace_name = 'keyspace_name_here' column_meta.table.columns = {column_meta.name: column_meta} parser = get_schema_parser(Mock(), '2.1.0', 0.1) row = {'index_name': 'index_name_here', 'index_type': 'index_type_here'} index_meta = parser._build_index_metadata(column_meta, row) self.assertEqual(index_meta.as_cql_query(), 'CREATE INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here)') row['index_options'] = '{ "class_name": "class_name_here" }' row['index_type'] = 'CUSTOM' index_meta = parser._build_index_metadata(column_meta, row) self.assertEqual(index_meta.as_cql_query(), "CREATE CUSTOM INDEX index_name_here ON keyspace_name_here.table_name_here (column_name_here) USING 'class_name_here'") class UnicodeIdentifiersTests(unittest.TestCase): """ Exercise cql generation with unicode characters. Keyspace, Table, and Index names cannot have special chars because C* names files by those identifiers, but they are tested anyway. Looking for encoding errors like PYTHON-447 """ name = six.text_type(b'\'_-()"\xc2\xac'.decode('utf-8')) def test_keyspace_name(self): km = KeyspaceMetadata(self.name, False, 'SimpleStrategy', {'replication_factor': 1}) km.export_as_string() def test_table_name(self): tm = TableMetadata(self.name, self.name) tm.export_as_string() def test_column_name_single_partition(self): tm = TableMetadata('ks', 'table') cm = ColumnMetadata(tm, self.name, u'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) tm.export_as_string() def test_column_name_single_partition_single_clustering(self): tm = TableMetadata('ks', 'table') cm = ColumnMetadata(tm, self.name, u'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) cm = ColumnMetadata(tm, self.name + 'x', u'int') tm.columns[cm.name] = cm tm.clustering_key.append(cm) tm.export_as_string() def test_column_name_multiple_partition(self): tm = TableMetadata('ks', 'table') cm = ColumnMetadata(tm, self.name, u'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) cm = ColumnMetadata(tm, self.name + 'x', u'int') tm.columns[cm.name] = cm tm.partition_key.append(cm) tm.export_as_string() def test_index(self): im = IndexMetadata(self.name, self.name, self.name, kind='', index_options={'target': self.name}) im.export_as_string() im = IndexMetadata(self.name, self.name, self.name, kind='CUSTOM', index_options={'target': self.name, 'class_name': 'Class'}) im.export_as_string() def test_function(self): fm = Function(self.name, self.name, (u'int', u'int'), (u'x', u'y'), u'int', u'language', self.name, False) fm.export_as_string() def test_aggregate(self): am = Aggregate(self.name, self.name, (u'text',), self.name, u'text', self.name, self.name, u'text') am.export_as_string() def test_user_type(self): um = UserType(self.name, self.name, [self.name, self.name], [u'int', u'text']) um.export_as_string() class HostsTests(unittest.TestCase): def test_iterate_all_hosts_and_modify(self): """ PYTHON-572 """ metadata = Metadata() metadata.add_or_return_host(Host('dc1.1', SimpleConvictionPolicy)) metadata.add_or_return_host(Host('dc1.2', SimpleConvictionPolicy)) self.assertEqual(len(metadata.all_hosts()), 2) for host in metadata.all_hosts(): # this would previously raise in Py3 metadata.remove_host(host) self.assertEqual(len(metadata.all_hosts()), 0)