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