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,58 @@
1
+ require 'helper'
2
+ require 'cassanity/instrumentation/statsd'
3
+
4
+ describe Cassanity::Instrumentation::StatsdSubscriber do
5
+ let(:statsd_client) { Statsd.new }
6
+ let(:socket) { FakeUDPSocket.new }
7
+
8
+ let(:client) {
9
+ Cassanity::Client.new(CassanityServers, {
10
+ instrumenter: ActiveSupport::Notifications,
11
+ })
12
+ }
13
+
14
+ let(:keyspace) { client[:cassanity_test] }
15
+
16
+ let(:column_family) {
17
+ keyspace.column_family({
18
+ name: :apps,
19
+ schema: {
20
+ primary_key: :id,
21
+ columns: {
22
+ id: :timeuuid,
23
+ name: :text,
24
+ },
25
+ },
26
+ })
27
+ }
28
+
29
+ before do
30
+ described_class.client = statsd_client
31
+ keyspace.recreate
32
+ column_family.recreate
33
+ Thread.current[:statsd_socket] = socket
34
+ end
35
+
36
+ after do
37
+ described_class.client = nil
38
+ Thread.current[:statsd_socket] = nil
39
+ end
40
+
41
+ it "updates timers when cql calls happen" do
42
+ # Clear the socket so we don't count the operations required to re-create
43
+ # the keyspace and column family.
44
+ socket.clear
45
+
46
+ column_family.insert({
47
+ data: {
48
+ id: SimpleUUID::UUID.new,
49
+ name: 'GitHub.com',
50
+ },
51
+ })
52
+
53
+ socket.recv.first.should match(/cassanity\.cql\:\d+\|ms/)
54
+ socket.recv.first.should match(/cassanity\.command\.column_family_insert\.cql\:\d+\|ms/)
55
+ socket.recv.first.should match(/cassanity\.column_family\.apps\.cql\:\d+\|ms/)
56
+ socket.recv.first.should match(/cassanity\.column_family.apps.column_family_insert.cql\:\d+\|ms/)
57
+ end
58
+ end
@@ -6,7 +6,7 @@ describe Cassanity::Keyspace do
6
6
  let(:self_created_keyspace_name) { 'self_created' }
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
  let(:required_arguments) {
@@ -21,41 +21,41 @@ describe Cassanity::Keyspace do
21
21
  }
22
22
 
23
23
  before do
24
- client_drop_keyspace(driver, keyspace_name)
25
- client_create_keyspace(driver, keyspace_name)
24
+ driver_drop_keyspace(driver, keyspace_name)
25
+ driver_create_keyspace(driver, keyspace_name)
26
26
  end
27
27
 
28
28
  after do
29
- client_drop_keyspace(driver, keyspace_name)
30
- client_drop_keyspace(driver, self_created_keyspace_name)
29
+ driver_drop_keyspace(driver, keyspace_name)
30
+ driver_drop_keyspace(driver, self_created_keyspace_name)
31
31
  end
32
32
 
33
33
  it "can create" do
34
- client_keyspace?(driver, self_created_keyspace_name).should be_false
34
+ driver_keyspace?(driver, self_created_keyspace_name).should be_false
35
35
  instance = described_class.new(required_arguments.merge({
36
36
  name: self_created_keyspace_name,
37
37
  }))
38
38
  instance.create
39
- client_keyspace?(driver, self_created_keyspace_name).should be_true
39
+ driver_keyspace?(driver, self_created_keyspace_name).should be_true
40
40
  end
41
41
 
42
42
  it "knows if it exists" do
43
43
  subject.exists?.should be_true
44
- client_drop_keyspace(driver, keyspace_name)
44
+ driver_drop_keyspace(driver, keyspace_name)
45
45
  subject.exists?.should be_false
46
46
  end
47
47
 
48
48
  it "can recreate when not created" do
49
- client_drop_keyspace(driver, keyspace_name)
50
- client_keyspace?(driver, keyspace_name).should be_false
49
+ driver_drop_keyspace(driver, keyspace_name)
50
+ driver_keyspace?(driver, keyspace_name).should be_false
51
51
  subject.recreate
52
- client_keyspace?(driver, keyspace_name).should be_true
52
+ driver_keyspace?(driver, keyspace_name).should be_true
53
53
  end
54
54
 
55
55
  it "can recreate when already created" do
56
- client_keyspace?(driver, keyspace_name).should be_true
56
+ driver_keyspace?(driver, keyspace_name).should be_true
57
57
  subject.recreate
58
- client_keyspace?(driver, keyspace_name).should be_true
58
+ driver_keyspace?(driver, keyspace_name).should be_true
59
59
  end
60
60
 
61
61
  it "can use" do
@@ -66,14 +66,14 @@ describe Cassanity::Keyspace do
66
66
  end
67
67
 
68
68
  it "can drop" do
69
- client_keyspace?(driver, keyspace_name).should be_true
69
+ driver_keyspace?(driver, keyspace_name).should be_true
70
70
  subject.drop
71
- client_keyspace?(driver, keyspace_name).should be_false
71
+ driver_keyspace?(driver, keyspace_name).should be_false
72
72
  end
73
73
 
74
74
  it "knows column families" do
75
- client_create_column_family(driver, 'something1')
76
- client_create_column_family(driver, 'something2')
75
+ driver_create_column_family(driver, :something1)
76
+ driver_create_column_family(driver, :something2)
77
77
 
78
78
  result = subject.column_families
79
79
  result.each do |column_family|
@@ -82,10 +82,10 @@ describe Cassanity::Keyspace do
82
82
  end
83
83
 
84
84
  names = result.map(&:name)
85
- names.should include('something1')
86
- names.should include('something2')
85
+ names.should include(:something1)
86
+ names.should include(:something2)
87
87
 
88
- client_drop_column_family(driver, 'something1')
89
- client_drop_column_family(driver, 'something2')
88
+ driver_drop_column_family(driver, :something1)
89
+ driver_drop_column_family(driver, :something2)
90
90
  end
91
91
  end
@@ -0,0 +1,157 @@
1
+ require 'helper'
2
+ require 'cassanity/migrator'
3
+
4
+ describe Cassanity::Migration do
5
+ let(:client) { Cassanity::Client.new(CassanityServers) }
6
+ let(:driver) { client.driver }
7
+ let(:keyspace) { client[:cassanity_test] }
8
+ let(:migrator) { Cassanity::Migrator.new(keyspace, '/fake') }
9
+ let(:migration) { Class.new(described_class).new(migrator) }
10
+
11
+ before do
12
+ driver_drop_keyspace(driver, keyspace.name)
13
+ driver_create_keyspace(driver, keyspace.name)
14
+ end
15
+
16
+ describe "#create_column_family" do
17
+ before do
18
+ migration.create_column_family(:users, {
19
+ primary_key: :id,
20
+ columns: {
21
+ id: :timeuuid,
22
+ name: :text,
23
+ age: :int,
24
+ },
25
+ })
26
+ end
27
+
28
+ it "creates a column family" do
29
+ keyspace[:users].exists?.should be_true
30
+ end
31
+ end
32
+
33
+ describe "#drop_column_family" do
34
+ before do
35
+ keyspace.column_family(:users, schema: {
36
+ primary_key: :id,
37
+ columns: {
38
+ id: :timeuuid,
39
+ name: :text,
40
+ age: :int,
41
+ },
42
+ }).create
43
+
44
+ migration.drop_column_family :users
45
+ end
46
+
47
+ it "drops column family" do
48
+ keyspace[:users].exists?.should be_false
49
+ end
50
+ end
51
+
52
+ describe "#add_column" do
53
+ before do
54
+ keyspace.column_family(:users, schema: {
55
+ primary_key: :id,
56
+ columns: {
57
+ id: :timeuuid,
58
+ name: :text,
59
+ age: :int,
60
+ },
61
+ }).create
62
+
63
+ migration.add_column :users, :email, :text
64
+ end
65
+
66
+ it "adds column" do
67
+ columns = keyspace[:users].columns
68
+ column = columns.detect { |column| column.name == :email }
69
+ column.should_not be_nil
70
+ column.type.should be(:text)
71
+ end
72
+ end
73
+
74
+ describe "#drop_column" do
75
+ before do
76
+ keyspace.column_family(:users, schema: {
77
+ primary_key: :id,
78
+ columns: {
79
+ id: :timeuuid,
80
+ name: :text,
81
+ age: :int,
82
+ },
83
+ }).create
84
+
85
+ migration.drop_column :users, :age
86
+ end
87
+
88
+ it "drops column" do
89
+ columns = keyspace[:users].columns
90
+ column = columns.detect { |column| column.name == :age }
91
+ column.should be_nil
92
+ end
93
+ end
94
+
95
+ describe "#alter_column_family" do
96
+ before do
97
+ keyspace.column_family(:users, schema: {
98
+ primary_key: :id,
99
+ columns: {
100
+ id: :timeuuid,
101
+ name: :text,
102
+ age: :int,
103
+ },
104
+ }).create
105
+
106
+ migration.alter_column_family :users, drop: :age
107
+ end
108
+
109
+ it "alters the column family" do
110
+ columns = keyspace[:users].columns
111
+ column = columns.detect { |column| column.name == :age }
112
+ column.should be_nil
113
+ end
114
+ end
115
+
116
+ describe "#add_index" do
117
+ before do
118
+ keyspace.column_family(:users, schema: {
119
+ primary_key: :id,
120
+ columns: {
121
+ id: :timeuuid,
122
+ email: :text,
123
+ },
124
+ }).create
125
+
126
+ migration.add_index :users, :email, name: :users_email_index
127
+ end
128
+
129
+ it "adds index" do
130
+ meta = driver.schema.column_families['users'].column_metadata
131
+ index = meta.detect { |c| c.index_name == 'users_email_index' }
132
+ index.should_not be_nil
133
+ end
134
+ end
135
+
136
+ describe "#drop_index" do
137
+ before do
138
+ column_family = keyspace.column_family(:users, schema: {
139
+ primary_key: :id,
140
+ columns: {
141
+ id: :timeuuid,
142
+ email: :text,
143
+ },
144
+ })
145
+ column_family.create
146
+ column_family.create_index(column_name: :email, name: :users_email_index)
147
+
148
+ migration.drop_index :users, :users_email_index
149
+ end
150
+
151
+ it "drops index" do
152
+ meta = driver.schema.column_families['users'].column_metadata
153
+ index = meta.detect { |c| c.index_name == 'users_email_index' }
154
+ index.should be_nil
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,212 @@
1
+ require 'helper'
2
+ require 'cassanity/migrator'
3
+
4
+ describe Cassanity::Migrator do
5
+ let(:client) { Cassanity::Client.new(CassanityServers) }
6
+ let(:driver) { client.driver }
7
+ let(:keyspace) { client[:cassanity_test] }
8
+ let(:column_family) { subject.column_family }
9
+
10
+ let(:migrations_path) {
11
+ Pathname(__FILE__).dirname.join('fixtures', 'migrations')
12
+ }
13
+
14
+ let(:log_string) { StringIO.new }
15
+
16
+ let(:logger) {
17
+ logger = Logger.new(log_string)
18
+ logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
19
+ logger
20
+ }
21
+
22
+ subject {
23
+ described_class.new(keyspace, migrations_path, {
24
+ logger: logger,
25
+ })
26
+ }
27
+
28
+ before do
29
+ driver_drop_keyspace(driver, keyspace.name)
30
+ driver_create_keyspace(driver, keyspace.name)
31
+ end
32
+
33
+ describe "#migrate" do
34
+ context "when no migrations have been run" do
35
+ before do
36
+ @result = subject.migrate
37
+ end
38
+
39
+ it "runs all migrations" do
40
+ @result[:performed].size.should be(3)
41
+ end
42
+
43
+ it "stores migrations in column family" do
44
+ rows = column_family.select
45
+ rows.size.should be(3)
46
+
47
+ version_to_name = {
48
+ '20130224135000' => 'create_users',
49
+ '20130225135002' => 'create_apps',
50
+ '20130226135004' => 'add_username_to_users',
51
+ }
52
+
53
+ rows.each do |row|
54
+ version_to_name.fetch(row['version']).should eq(row['name'])
55
+ end
56
+ end
57
+
58
+ it "executes migrations" do
59
+ keyspace[:users].exists?.should be_true
60
+ keyspace[:apps].exists?.should be_true
61
+ end
62
+ end
63
+
64
+ context "when some migrations have been run" do
65
+ before do
66
+ subject.migrate_to subject.migrations[1].version
67
+ @result = subject.migrate
68
+ end
69
+
70
+ it "runs only migrations that need to be" do
71
+ versions = @result[:performed].map(&:version)
72
+ versions.should eq([subject.migrations[2].version])
73
+ end
74
+ end
75
+
76
+ context "when migration has not been run that is older than migrations that have been run" do
77
+ before do
78
+ subject.migrated_up subject.migrations[0]
79
+ subject.migrated_up subject.migrations[2]
80
+ @result = subject.migrate
81
+ end
82
+
83
+ it "runs migration that has not been run" do
84
+ versions = @result[:performed].map(&:version)
85
+ versions.should eq([subject.migrations[1].version])
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "#migrate_to" do
91
+ context "migrating to specific versions" do
92
+ it "works" do
93
+ subject.migrate_to(subject.migrations[0].version)
94
+ subject.performed_migrations.size.should be(1)
95
+
96
+ subject.migrate_to(subject.migrations[1].version)
97
+ subject.performed_migrations.size.should be(2)
98
+
99
+ subject.migrate_to(subject.migrations[2].version)
100
+ subject.performed_migrations.size.should be(3)
101
+
102
+ subject.migrate_to(subject.migrations[1].version, :down)
103
+ subject.performed_migrations.size.should be(2)
104
+
105
+ subject.migrate_to(subject.migrations[0].version, :down)
106
+ subject.performed_migrations.size.should be(1)
107
+
108
+ # works with string
109
+ subject.migrate_to(subject.migrations[0].version - 1, 'down')
110
+ subject.performed_migrations.size.should be(0)
111
+ end
112
+
113
+ it "raises argument error for invalid direction" do
114
+ expect {
115
+ subject.migrate_to(0, 'nooope')
116
+ }.to raise_error(ArgumentError, ':nooope is not a valid migration direction')
117
+ end
118
+
119
+ it "returns migrations in the order they were performed" do
120
+ result = subject.migrate_to(subject.migrations[2].version)
121
+ result[:performed].map(&:version).should eq([
122
+ subject.migrations[0].version,
123
+ subject.migrations[1].version,
124
+ subject.migrations[2].version,
125
+ ])
126
+
127
+ result = subject.migrate_to(0, :down)
128
+ result[:performed].map(&:version).should eq([
129
+ subject.migrations[2].version,
130
+ subject.migrations[1].version,
131
+ subject.migrations[0].version,
132
+ ])
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#migrated_up" do
138
+ it "adds migration to performed migrations" do
139
+ migration = subject.migrations[0]
140
+ subject.migrated_up(migration)
141
+ names = subject.column_family.select.map { |row| row['name'] }
142
+ names.should include(migration.name)
143
+ end
144
+ end
145
+
146
+ describe "#migrated_down" do
147
+ it "removes migration from performed migrations" do
148
+ migration = subject.migrations[0]
149
+ subject.column_family.insert(data: {
150
+ version: migration.version,
151
+ name: migration.name,
152
+ migrated_at: Time.now,
153
+ })
154
+ subject.migrated_down(migration)
155
+ names = subject.column_family.select.map { |row| row['name'] }
156
+ names.should_not include(migration.name)
157
+ end
158
+ end
159
+
160
+ describe "#migrations" do
161
+ it "returns all migrations in order from the migrations path" do
162
+ names = subject.migrations.map(&:name)
163
+ names.should eq([
164
+ 'create_users',
165
+ 'create_apps',
166
+ 'add_username_to_users',
167
+ ])
168
+ end
169
+ end
170
+
171
+ describe "#performed_migrations" do
172
+ it "returns all performed migrations in order" do
173
+ subject.migrations.each do |migration|
174
+ subject.column_family.insert(data: {
175
+ name: migration.name,
176
+ version: migration.version,
177
+ migrated_at: Time.now,
178
+ })
179
+ end
180
+
181
+ names = subject.performed_migrations.map(&:name)
182
+ names.should eq([
183
+ 'create_users',
184
+ 'create_apps',
185
+ 'add_username_to_users',
186
+ ])
187
+ end
188
+ end
189
+
190
+ describe "#pending_migrations" do
191
+ it "returns all pending migrations in order" do
192
+ migration = subject.migrations[0]
193
+ subject.column_family.insert(data: {
194
+ name: migration.name,
195
+ version: migration.version,
196
+ migrated_at: Time.now,
197
+ })
198
+ names = subject.pending_migrations.map(&:name)
199
+ names.should eq([
200
+ 'create_apps',
201
+ 'add_username_to_users',
202
+ ])
203
+ end
204
+ end
205
+
206
+ describe "#log" do
207
+ it "sends message to logger" do
208
+ subject.log('just testing')
209
+ log_string.string.should match("just testing")
210
+ end
211
+ end
212
+ end