cassanity 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +1 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +0 -2
  5. data/README.md +11 -0
  6. data/doc/Instrumentation.md +40 -0
  7. data/doc/Migrations.md +132 -0
  8. data/examples/keyspaces.rb +11 -7
  9. data/lib/cassanity/argument_generators/column_family_delete.rb +1 -1
  10. data/lib/cassanity/argument_generators/columns.rb +33 -0
  11. data/lib/cassanity/argument_generators/where_clause.rb +1 -1
  12. data/lib/cassanity/client.rb +3 -1
  13. data/lib/cassanity/column.rb +48 -0
  14. data/lib/cassanity/column_family.rb +21 -2
  15. data/lib/cassanity/connection.rb +4 -8
  16. data/lib/cassanity/error.rb +18 -11
  17. data/lib/cassanity/executors/cassandra_cql.rb +79 -50
  18. data/lib/cassanity/instrumentation/log_subscriber.rb +4 -5
  19. data/lib/cassanity/instrumentation/metriks.rb +6 -0
  20. data/lib/cassanity/instrumentation/metriks_subscriber.rb +16 -0
  21. data/lib/cassanity/instrumentation/statsd.rb +6 -0
  22. data/lib/cassanity/instrumentation/statsd_subscriber.rb +22 -0
  23. data/lib/cassanity/instrumentation/subscriber.rb +58 -0
  24. data/lib/cassanity/instrumenters/memory.rb +0 -1
  25. data/lib/cassanity/keyspace.rb +10 -8
  26. data/lib/cassanity/migration.rb +125 -0
  27. data/lib/cassanity/migration_proxy.rb +76 -0
  28. data/lib/cassanity/migrator.rb +154 -0
  29. data/lib/cassanity/result_transformers/column_families.rb +20 -0
  30. data/lib/cassanity/result_transformers/columns.rb +21 -0
  31. data/lib/cassanity/result_transformers/keyspaces.rb +21 -0
  32. data/lib/cassanity/result_transformers/mirror.rb +1 -1
  33. data/lib/cassanity/result_transformers/result_to_array.rb +1 -1
  34. data/lib/cassanity/retry_strategies/exponential_backoff.rb +43 -0
  35. data/lib/cassanity/retry_strategies/retry_n_times.rb +29 -0
  36. data/lib/cassanity/retry_strategies/retry_strategy.rb +35 -0
  37. data/lib/cassanity/version.rb +1 -1
  38. data/spec/helper.rb +8 -0
  39. data/spec/integration/cassanity/column_family_spec.rb +36 -25
  40. data/spec/integration/cassanity/connection_spec.rb +11 -11
  41. data/spec/integration/cassanity/fixtures/migrations/20130224135000_create_users.rb +17 -0
  42. data/spec/integration/cassanity/fixtures/migrations/20130225135002_create_apps.rb +15 -0
  43. data/spec/integration/cassanity/fixtures/migrations/20130226135004_add_username_to_users.rb +9 -0
  44. data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +71 -0
  45. data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +48 -0
  46. data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +58 -0
  47. data/spec/integration/cassanity/keyspace_spec.rb +21 -21
  48. data/spec/integration/cassanity/migration_spec.rb +157 -0
  49. data/spec/integration/cassanity/migrator_spec.rb +212 -0
  50. data/spec/support/cassanity_helpers.rb +21 -17
  51. data/spec/support/fake_udp_socket.rb +27 -0
  52. data/spec/unit/cassanity/argument_generators/batch_spec.rb +5 -5
  53. data/spec/unit/cassanity/argument_generators/column_family_delete_spec.rb +20 -6
  54. data/spec/unit/cassanity/argument_generators/column_family_update_spec.rb +6 -6
  55. data/spec/unit/cassanity/argument_generators/columns_spec.rb +45 -0
  56. data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +1 -1
  57. data/spec/unit/cassanity/argument_generators/keyspace_drop_spec.rb +1 -1
  58. data/spec/unit/cassanity/argument_generators/keyspace_use_spec.rb +1 -1
  59. data/spec/unit/cassanity/argument_generators/where_clause_spec.rb +2 -2
  60. data/spec/unit/cassanity/client_spec.rb +10 -3
  61. data/spec/unit/cassanity/column_family_spec.rb +20 -3
  62. data/spec/unit/cassanity/column_spec.rb +76 -0
  63. data/spec/unit/cassanity/connection_spec.rb +1 -1
  64. data/spec/unit/cassanity/error_spec.rb +7 -2
  65. data/spec/unit/cassanity/executors/cassandra_cql_spec.rb +76 -23
  66. data/spec/unit/cassanity/keyspace_spec.rb +38 -13
  67. data/spec/unit/cassanity/migration_proxy_spec.rb +81 -0
  68. data/spec/unit/cassanity/migration_spec.rb +12 -0
  69. data/spec/unit/cassanity/migrator_spec.rb +20 -0
  70. data/spec/unit/cassanity/retry_strategies/exponential_backoff_spec.rb +37 -0
  71. data/spec/unit/cassanity/retry_strategies/retry_n_times_spec.rb +47 -0
  72. data/spec/unit/cassanity/retry_strategies/retry_strategy_spec.rb +27 -0
  73. 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
@@ -1,3 +1,3 @@
1
1
  module Cassanity
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
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) { 'apps' }
7
- let(:counters_column_family_name) { 'counters' }
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
- client_drop_keyspace(driver, keyspace_name)
37
- client_create_keyspace(driver, keyspace_name)
38
- client_create_column_family(driver, column_family_name, "id text PRIMARY KEY, name text")
39
- client_create_column_family(driver, counters_column_family_name, "id text PRIMARY KEY, views counter")
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
- client_drop_keyspace(driver, keyspace_name)
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
- client_drop_column_family(driver, column_family_name)
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
- client_drop_column_family(driver, column_family_name)
54
- client_column_family?(driver, column_family_name).should be_false
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
- client_column_family?(driver, column_family_name).should be_true
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
- client_column_family?(driver, column_family_name).should be_true
60
+ driver_column_family?(driver, column_family_name).should be_true
61
61
  subject.recreate
62
- client_column_family?(driver, column_family_name).should be_true
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
- apps_column_family = driver.schema.column_families.fetch(column_family.name)
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
- client_column_family?(driver, column_family_name).should be_true
93
+ driver_column_family?(driver, column_family_name).should be_true
93
94
  subject.drop
94
- client_column_family?(driver, column_family_name).should be_false
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
- client_column_family?(driver, column_family_name).should be_true
99
+ driver_column_family?(driver, column_family_name).should be_true
99
100
  driver.execute('USE system')
100
101
  subject.drop
101
- client_column_family?(driver, column_family_name).should be_false
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
- apps_column_family = driver.schema.column_families.fetch(column_family_name)
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
- apps_column_family = driver.schema.column_families.fetch(column_family_name)
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
- apps_column_family = driver.schema.column_families.fetch(column_family_name)
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
- apps_column_family = driver.schema.column_families.fetch(column_family_name)
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['apps']
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['apps']
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
- client_drop_keyspace(driver, keyspace_name)
15
+ driver_drop_keyspace(driver, keyspace_name)
16
16
  end
17
17
 
18
18
  after do
19
- client_drop_keyspace(driver, keyspace_name)
19
+ driver_drop_keyspace(driver, keyspace_name)
20
20
  end
21
21
 
22
22
  it "can batch" do
23
- client_create_keyspace(driver, keyspace_name)
24
- client_create_column_family(driver, column_family_name, "id text PRIMARY KEY, name text")
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
- client_create_keyspace(driver, 'something1')
50
- client_create_keyspace(driver, 'something2')
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('something1')
60
- names.should include('something2')
59
+ names.should include(:something1)
60
+ names.should include(:something2)
61
61
 
62
- client_drop_keyspace(driver, 'something1')
63
- client_drop_keyspace(driver, 'something2')
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,15 @@
1
+ class CreateApps < Cassanity::Migration
2
+ def up
3
+ create_column_family :apps, {
4
+ primary_key: :id,
5
+ columns: {
6
+ id: :timeuuid,
7
+ name: :text,
8
+ },
9
+ }
10
+ end
11
+
12
+ def down
13
+ drop_column_family :apps
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ class AddUsernameToUsers < Cassanity::Migration
2
+ def up
3
+ add_column :users, :username, :text
4
+ end
5
+
6
+ def down
7
+ drop_column :users, :username
8
+ end
9
+ 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