cassanity 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|