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.
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