cassanity 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -1
- data/.travis.yml +1 -0
- data/Gemfile +3 -0
- data/Guardfile +0 -2
- data/README.md +11 -0
- data/doc/Instrumentation.md +40 -0
- data/doc/Migrations.md +132 -0
- data/examples/keyspaces.rb +11 -7
- data/lib/cassanity/argument_generators/column_family_delete.rb +1 -1
- data/lib/cassanity/argument_generators/columns.rb +33 -0
- data/lib/cassanity/argument_generators/where_clause.rb +1 -1
- data/lib/cassanity/client.rb +3 -1
- data/lib/cassanity/column.rb +48 -0
- data/lib/cassanity/column_family.rb +21 -2
- data/lib/cassanity/connection.rb +4 -8
- data/lib/cassanity/error.rb +18 -11
- data/lib/cassanity/executors/cassandra_cql.rb +79 -50
- data/lib/cassanity/instrumentation/log_subscriber.rb +4 -5
- data/lib/cassanity/instrumentation/metriks.rb +6 -0
- data/lib/cassanity/instrumentation/metriks_subscriber.rb +16 -0
- data/lib/cassanity/instrumentation/statsd.rb +6 -0
- data/lib/cassanity/instrumentation/statsd_subscriber.rb +22 -0
- data/lib/cassanity/instrumentation/subscriber.rb +58 -0
- data/lib/cassanity/instrumenters/memory.rb +0 -1
- data/lib/cassanity/keyspace.rb +10 -8
- data/lib/cassanity/migration.rb +125 -0
- data/lib/cassanity/migration_proxy.rb +76 -0
- data/lib/cassanity/migrator.rb +154 -0
- data/lib/cassanity/result_transformers/column_families.rb +20 -0
- data/lib/cassanity/result_transformers/columns.rb +21 -0
- data/lib/cassanity/result_transformers/keyspaces.rb +21 -0
- data/lib/cassanity/result_transformers/mirror.rb +1 -1
- data/lib/cassanity/result_transformers/result_to_array.rb +1 -1
- data/lib/cassanity/retry_strategies/exponential_backoff.rb +43 -0
- data/lib/cassanity/retry_strategies/retry_n_times.rb +29 -0
- data/lib/cassanity/retry_strategies/retry_strategy.rb +35 -0
- data/lib/cassanity/version.rb +1 -1
- data/spec/helper.rb +8 -0
- data/spec/integration/cassanity/column_family_spec.rb +36 -25
- data/spec/integration/cassanity/connection_spec.rb +11 -11
- data/spec/integration/cassanity/fixtures/migrations/20130224135000_create_users.rb +17 -0
- data/spec/integration/cassanity/fixtures/migrations/20130225135002_create_apps.rb +15 -0
- data/spec/integration/cassanity/fixtures/migrations/20130226135004_add_username_to_users.rb +9 -0
- data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +71 -0
- data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +48 -0
- data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +58 -0
- data/spec/integration/cassanity/keyspace_spec.rb +21 -21
- data/spec/integration/cassanity/migration_spec.rb +157 -0
- data/spec/integration/cassanity/migrator_spec.rb +212 -0
- data/spec/support/cassanity_helpers.rb +21 -17
- data/spec/support/fake_udp_socket.rb +27 -0
- data/spec/unit/cassanity/argument_generators/batch_spec.rb +5 -5
- data/spec/unit/cassanity/argument_generators/column_family_delete_spec.rb +20 -6
- data/spec/unit/cassanity/argument_generators/column_family_update_spec.rb +6 -6
- data/spec/unit/cassanity/argument_generators/columns_spec.rb +45 -0
- data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/keyspace_drop_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/keyspace_use_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/where_clause_spec.rb +2 -2
- data/spec/unit/cassanity/client_spec.rb +10 -3
- data/spec/unit/cassanity/column_family_spec.rb +20 -3
- data/spec/unit/cassanity/column_spec.rb +76 -0
- data/spec/unit/cassanity/connection_spec.rb +1 -1
- data/spec/unit/cassanity/error_spec.rb +7 -2
- data/spec/unit/cassanity/executors/cassandra_cql_spec.rb +76 -23
- data/spec/unit/cassanity/keyspace_spec.rb +38 -13
- data/spec/unit/cassanity/migration_proxy_spec.rb +81 -0
- data/spec/unit/cassanity/migration_spec.rb +12 -0
- data/spec/unit/cassanity/migrator_spec.rb +20 -0
- data/spec/unit/cassanity/retry_strategies/exponential_backoff_spec.rb +37 -0
- data/spec/unit/cassanity/retry_strategies/retry_n_times_spec.rb +47 -0
- data/spec/unit/cassanity/retry_strategies/retry_strategy_spec.rb +27 -0
- metadata +56 -4
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'cassanity/retry_strategies/retry_strategy'
|
2
|
+
|
3
|
+
module Cassanity
|
4
|
+
module RetryStrategies
|
5
|
+
class RetryNTimes < RetryStrategy
|
6
|
+
# Private
|
7
|
+
attr_reader :retries
|
8
|
+
|
9
|
+
# Public: initialize the retry strategy.
|
10
|
+
#
|
11
|
+
# args - The Hash of arguments.
|
12
|
+
# :retries - the number of times to retry an unsuccessful call
|
13
|
+
# before failing.
|
14
|
+
def initialize(args = {})
|
15
|
+
# By default, there's no retries - if the call fails, you
|
16
|
+
# get the error propagated to you.
|
17
|
+
@retries = args[:retries] || 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# Private: re-raise the exception from the last call if it's been retried
|
21
|
+
# more than the maximum allowed amount.
|
22
|
+
def fail(attempts, error)
|
23
|
+
if attempts > @retries
|
24
|
+
raise error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Cassanity
|
2
|
+
module RetryStrategies
|
3
|
+
class RetryStrategy
|
4
|
+
# Internal: override in subclass.
|
5
|
+
#
|
6
|
+
# attempts - how many times the unsuccessful call has been tried so far
|
7
|
+
# last_error - the error raised by the unsuccessful call
|
8
|
+
def fail(attempts, last_error)
|
9
|
+
raise 'not implemented'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: execute the given block, and handle errors raised
|
13
|
+
# by the CassandraCQL driver. Call the retry method (overridden in your
|
14
|
+
# subclass) on each failed attempt with a current retry count and
|
15
|
+
# the error raised by the block.
|
16
|
+
#
|
17
|
+
# payload - A Hash from an instrumenter to store a retry count in, or nil if
|
18
|
+
# the number of retries shouldn't be tracked.
|
19
|
+
def execute(payload = nil)
|
20
|
+
return unless block_given?
|
21
|
+
|
22
|
+
attempt = 0
|
23
|
+
|
24
|
+
while attempt += 1
|
25
|
+
begin
|
26
|
+
payload[:attempts] = attempt unless payload.nil?
|
27
|
+
return yield
|
28
|
+
rescue CassandraCQL::Error::InvalidRequestException, Thrift::Exception => e
|
29
|
+
fail(attempt, e)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/cassanity/version.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
2
|
|
3
|
+
require 'pathname'
|
3
4
|
require 'rubygems'
|
4
5
|
require 'bundler'
|
5
6
|
|
6
7
|
Bundler.require :default
|
8
|
+
Dotenv.load
|
7
9
|
|
8
10
|
require 'cassanity'
|
9
11
|
|
@@ -18,6 +20,7 @@ RSpec.configure do |config|
|
|
18
20
|
config.alias_example_to :fit, :focused => true
|
19
21
|
config.alias_example_to :xit, :pending => true
|
20
22
|
config.run_all_when_everything_filtered = true
|
23
|
+
config.fail_fast = true
|
21
24
|
|
22
25
|
config.backtrace_clean_patterns = [
|
23
26
|
/lib\/rspec\/(core|expectations|matchers|mocks)/,
|
@@ -25,3 +28,8 @@ RSpec.configure do |config|
|
|
25
28
|
|
26
29
|
config.include CassanityHelpers
|
27
30
|
end
|
31
|
+
|
32
|
+
host = ENV.fetch('CASSANITY_HOST', '127.0.0.1')
|
33
|
+
port = ENV.fetch('CASSANITY_PORT', '9160')
|
34
|
+
|
35
|
+
CassanityServers = "#{host}:#{port}"
|
@@ -3,10 +3,10 @@ require 'cassanity/keyspace'
|
|
3
3
|
|
4
4
|
describe Cassanity::ColumnFamily do
|
5
5
|
let(:keyspace_name) { 'cassanity_test' }
|
6
|
-
let(:column_family_name) {
|
7
|
-
let(:counters_column_family_name) {
|
6
|
+
let(:column_family_name) { :apps }
|
7
|
+
let(:counters_column_family_name) { :counters }
|
8
8
|
|
9
|
-
let(:client) { Cassanity::Client.new }
|
9
|
+
let(:client) { Cassanity::Client.new(CassanityServers) }
|
10
10
|
let(:driver) { client.driver }
|
11
11
|
|
12
12
|
let(:keyspace) { client[keyspace_name] }
|
@@ -33,40 +33,41 @@ describe Cassanity::ColumnFamily do
|
|
33
33
|
}
|
34
34
|
|
35
35
|
before do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
driver_drop_keyspace(driver, keyspace_name)
|
37
|
+
driver_create_keyspace(driver, keyspace_name)
|
38
|
+
driver_create_column_family(driver, column_family_name, "id text PRIMARY KEY, name text")
|
39
|
+
driver_create_column_family(driver, counters_column_family_name, "id text PRIMARY KEY, views counter")
|
40
40
|
end
|
41
41
|
|
42
42
|
after do
|
43
|
-
|
43
|
+
driver_drop_keyspace(driver, keyspace_name)
|
44
44
|
end
|
45
45
|
|
46
46
|
it "knows if it exists" do
|
47
47
|
subject.exists?.should be_true
|
48
|
-
|
48
|
+
driver_drop_column_family(driver, column_family_name)
|
49
49
|
subject.exists?.should be_false
|
50
50
|
end
|
51
51
|
|
52
52
|
it "can recreate when not created" do
|
53
|
-
|
54
|
-
|
53
|
+
driver_drop_column_family(driver, column_family_name)
|
54
|
+
driver_column_family?(driver, column_family_name).should be_false
|
55
55
|
subject.recreate
|
56
|
-
|
56
|
+
driver_column_family?(driver, column_family_name).should be_true
|
57
57
|
end
|
58
58
|
|
59
59
|
it "can recreate when already created" do
|
60
|
-
|
60
|
+
driver_column_family?(driver, column_family_name).should be_true
|
61
61
|
subject.recreate
|
62
|
-
|
62
|
+
driver_column_family?(driver, column_family_name).should be_true
|
63
63
|
end
|
64
64
|
|
65
65
|
it "can create itself" do
|
66
66
|
column_family = described_class.new(arguments.merge(name: 'people'))
|
67
67
|
column_family.create
|
68
68
|
|
69
|
-
|
69
|
+
column_families = driver.schema.column_families
|
70
|
+
apps_column_family = column_families.fetch(column_family.name.to_s)
|
70
71
|
apps_column_family.comment.should eq('For storing things')
|
71
72
|
|
72
73
|
columns = apps_column_family.columns
|
@@ -89,41 +90,45 @@ describe Cassanity::ColumnFamily do
|
|
89
90
|
end
|
90
91
|
|
91
92
|
it "can drop" do
|
92
|
-
|
93
|
+
driver_column_family?(driver, column_family_name).should be_true
|
93
94
|
subject.drop
|
94
|
-
|
95
|
+
driver_column_family?(driver, column_family_name).should be_false
|
95
96
|
end
|
96
97
|
|
97
98
|
it "can drop when using a different keyspace" do
|
98
|
-
|
99
|
+
driver_column_family?(driver, column_family_name).should be_true
|
99
100
|
driver.execute('USE system')
|
100
101
|
subject.drop
|
101
|
-
|
102
|
+
driver_column_family?(driver, column_family_name).should be_false
|
102
103
|
end
|
103
104
|
|
104
105
|
it "can alter" do
|
105
106
|
subject.alter(add: {created_at: :timestamp})
|
106
107
|
|
107
|
-
|
108
|
+
column_families = driver.schema.column_families
|
109
|
+
apps_column_family = column_families.fetch(column_family_name.to_s)
|
108
110
|
columns = apps_column_family.columns
|
109
111
|
columns.should have_key('created_at')
|
110
112
|
columns['created_at'].should eq('org.apache.cassandra.db.marshal.DateType')
|
111
113
|
|
112
114
|
subject.alter(alter: {created_at: :timeuuid})
|
113
115
|
|
114
|
-
|
116
|
+
column_families = driver.schema.column_families
|
117
|
+
apps_column_family = column_families.fetch(column_family_name.to_s)
|
115
118
|
columns = apps_column_family.columns
|
116
119
|
columns.should have_key('created_at')
|
117
120
|
columns['created_at'].should eq('org.apache.cassandra.db.marshal.TimeUUIDType')
|
118
121
|
|
119
122
|
subject.alter(drop: :created_at)
|
120
123
|
|
121
|
-
|
124
|
+
column_families = driver.schema.column_families
|
125
|
+
apps_column_family = column_families.fetch(column_family_name.to_s)
|
122
126
|
columns = apps_column_family.columns
|
123
127
|
columns.should_not have_key('created_at')
|
124
128
|
|
125
129
|
subject.alter(with: {comment: 'Some new comment'})
|
126
|
-
|
130
|
+
column_families = driver.schema.column_families
|
131
|
+
apps_column_family = column_families.fetch(column_family_name.to_s)
|
127
132
|
apps_column_family.comment.should eq('Some new comment')
|
128
133
|
end
|
129
134
|
|
@@ -133,7 +138,7 @@ describe Cassanity::ColumnFamily do
|
|
133
138
|
column_name: :name,
|
134
139
|
})
|
135
140
|
|
136
|
-
apps = driver.schema.column_families
|
141
|
+
apps = driver.schema.column_families.fetch(column_family_name.to_s)
|
137
142
|
apps_meta = apps.column_metadata
|
138
143
|
index = apps_meta.detect { |c| c.index_name == 'apps_name_index' }
|
139
144
|
index.should_not be_nil
|
@@ -142,7 +147,7 @@ describe Cassanity::ColumnFamily do
|
|
142
147
|
name: :apps_name_index,
|
143
148
|
})
|
144
149
|
|
145
|
-
apps = driver.schema.column_families
|
150
|
+
apps = driver.schema.column_families.fetch(column_family_name.to_s)
|
146
151
|
apps_meta = apps.column_metadata
|
147
152
|
index = apps_meta.detect { |c| c.index_name == 'apps_name_index' }
|
148
153
|
index.should be_nil
|
@@ -307,4 +312,10 @@ describe Cassanity::ColumnFamily do
|
|
307
312
|
result = driver.execute("SELECT * FROM #{column_family_name} WHERE id = '2'")
|
308
313
|
result.rows.should eq(1)
|
309
314
|
end
|
315
|
+
|
316
|
+
it "can get columns" do
|
317
|
+
columns = subject.columns
|
318
|
+
columns.map(&:name).should eq([:name])
|
319
|
+
columns.map(&:type).should eq([:text])
|
320
|
+
end
|
310
321
|
end
|
@@ -6,22 +6,22 @@ describe Cassanity::Connection do
|
|
6
6
|
let(:keyspace_name) { 'cassanity_test' }
|
7
7
|
let(:column_family_name) { 'apps' }
|
8
8
|
|
9
|
-
let(:client) { Cassanity::Client.new }
|
9
|
+
let(:client) { Cassanity::Client.new(CassanityServers) }
|
10
10
|
let(:driver) { client.driver }
|
11
11
|
|
12
12
|
subject { client.connection }
|
13
13
|
|
14
14
|
before do
|
15
|
-
|
15
|
+
driver_drop_keyspace(driver, keyspace_name)
|
16
16
|
end
|
17
17
|
|
18
18
|
after do
|
19
|
-
|
19
|
+
driver_drop_keyspace(driver, keyspace_name)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "can batch" do
|
23
|
-
|
24
|
-
|
23
|
+
driver_create_keyspace(driver, keyspace_name)
|
24
|
+
driver_create_column_family(driver, column_family_name, "id text PRIMARY KEY, name text")
|
25
25
|
|
26
26
|
subject.batch({
|
27
27
|
keyspace_name: keyspace_name,
|
@@ -46,8 +46,8 @@ describe Cassanity::Connection do
|
|
46
46
|
end
|
47
47
|
|
48
48
|
it "knows keyspaces" do
|
49
|
-
|
50
|
-
|
49
|
+
driver_create_keyspace(driver, :something1)
|
50
|
+
driver_create_keyspace(driver, :something2)
|
51
51
|
|
52
52
|
result = subject.keyspaces
|
53
53
|
result.each do |keyspace|
|
@@ -56,10 +56,10 @@ describe Cassanity::Connection do
|
|
56
56
|
end
|
57
57
|
|
58
58
|
names = result.map(&:name)
|
59
|
-
names.should include(
|
60
|
-
names.should include(
|
59
|
+
names.should include(:something1)
|
60
|
+
names.should include(:something2)
|
61
61
|
|
62
|
-
|
63
|
-
|
62
|
+
driver_drop_keyspace(driver, :something1)
|
63
|
+
driver_drop_keyspace(driver, :something2)
|
64
64
|
end
|
65
65
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateUsers < Cassanity::Migration
|
2
|
+
def up
|
3
|
+
create_column_family :users, {
|
4
|
+
primary_key: :id,
|
5
|
+
columns: {
|
6
|
+
id: :timeuuid,
|
7
|
+
name: :text,
|
8
|
+
age: :int,
|
9
|
+
updated_at: :timestamp,
|
10
|
+
},
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
drop_column_family :users
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'cassanity/instrumentation/log_subscriber'
|
3
|
+
|
4
|
+
describe Cassanity::Instrumentation::LogSubscriber do
|
5
|
+
let(:client) {
|
6
|
+
Cassanity::Client.new(CassanityServers, {
|
7
|
+
instrumenter: ActiveSupport::Notifications,
|
8
|
+
})
|
9
|
+
}
|
10
|
+
|
11
|
+
let(:keyspace) { client[:cassanity_test] }
|
12
|
+
|
13
|
+
let(:column_family) {
|
14
|
+
keyspace.column_family({
|
15
|
+
name: :apps,
|
16
|
+
schema: {
|
17
|
+
primary_key: :id,
|
18
|
+
columns: {
|
19
|
+
id: :timeuuid,
|
20
|
+
name: :text,
|
21
|
+
},
|
22
|
+
},
|
23
|
+
})
|
24
|
+
}
|
25
|
+
|
26
|
+
before do
|
27
|
+
@io = StringIO.new
|
28
|
+
Cassanity::Instrumentation::LogSubscriber.logger = Logger.new(@io)
|
29
|
+
|
30
|
+
keyspace.recreate
|
31
|
+
column_family.recreate
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
Cassanity::Instrumentation::LogSubscriber.logger = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "works" do
|
39
|
+
column_family.insert({
|
40
|
+
data: {
|
41
|
+
id: SimpleUUID::UUID.new,
|
42
|
+
name: 'GitHub.com',
|
43
|
+
},
|
44
|
+
})
|
45
|
+
|
46
|
+
query = "INSERT INTO cassanity_test.apps (id, name) VALUES (?, ?)"
|
47
|
+
log = @io.string
|
48
|
+
log.should match(/#{Regexp.escape(query)}/i)
|
49
|
+
log.should match(/UUID/i)
|
50
|
+
log.should match(/GitHub\.com/i)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "does not fail when no bind variables" do
|
54
|
+
client.keyspaces
|
55
|
+
query = "SELECT * FROM system.schema_keyspaces"
|
56
|
+
log = @io.string
|
57
|
+
log.should match(/#{Regexp.escape(query)}/i)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "works through exceptions" do
|
61
|
+
client.driver.should_receive(:execute).and_raise(StandardError.new('boom'))
|
62
|
+
begin
|
63
|
+
client.keyspaces
|
64
|
+
rescue StandardError => e
|
65
|
+
end
|
66
|
+
|
67
|
+
query = "SELECT * FROM system.schema_keyspaces"
|
68
|
+
log = @io.string.split("\n").last
|
69
|
+
log.should match(/#{Regexp.escape(query)}/i)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'cassanity/instrumentation/metriks'
|
3
|
+
|
4
|
+
describe Cassanity::Instrumentation::MetriksSubscriber do
|
5
|
+
let(:client) {
|
6
|
+
Cassanity::Client.new(CassanityServers, {
|
7
|
+
instrumenter: ActiveSupport::Notifications,
|
8
|
+
})
|
9
|
+
}
|
10
|
+
|
11
|
+
let(:keyspace) { client[:cassanity_test] }
|
12
|
+
|
13
|
+
let(:column_family) {
|
14
|
+
keyspace.column_family({
|
15
|
+
name: :apps,
|
16
|
+
schema: {
|
17
|
+
primary_key: :id,
|
18
|
+
columns: {
|
19
|
+
id: :timeuuid,
|
20
|
+
name: :text,
|
21
|
+
},
|
22
|
+
},
|
23
|
+
})
|
24
|
+
}
|
25
|
+
|
26
|
+
before do
|
27
|
+
keyspace.recreate
|
28
|
+
column_family.recreate
|
29
|
+
end
|
30
|
+
|
31
|
+
it "updates timers when cql calls happen" do
|
32
|
+
# Clear the registry so we don't count the operations required to re-create
|
33
|
+
# the keyspace and column family.
|
34
|
+
Metriks::Registry.default.clear
|
35
|
+
|
36
|
+
column_family.insert({
|
37
|
+
data: {
|
38
|
+
id: SimpleUUID::UUID.new,
|
39
|
+
name: 'GitHub.com',
|
40
|
+
},
|
41
|
+
})
|
42
|
+
|
43
|
+
Metriks.timer('cassanity.cql').count.should be(1)
|
44
|
+
Metriks.timer('cassanity.column_family.apps.cql').count.should be(1)
|
45
|
+
Metriks.timer('cassanity.command.column_family_insert.cql').count.should be(1)
|
46
|
+
Metriks.timer('cassanity.column_family.apps.column_family_insert.cql').count.should be(1)
|
47
|
+
end
|
48
|
+
end
|