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
@@ -2,7 +2,7 @@ require 'helper'
2
2
  require 'cassanity/connection'
3
3
 
4
4
  describe Cassanity::Connection do
5
- let(:keyspace_name) { 'analytics' }
5
+ let(:keyspace_name) { :analytics }
6
6
  let(:executor) {
7
7
  double('Executor', {
8
8
  call: nil,
@@ -2,7 +2,7 @@ require 'helper'
2
2
  require 'cassanity/error'
3
3
 
4
4
  describe Cassanity::Error do
5
- HorribleBadThing = Class.new(Exception)
5
+ HorribleBadThing = Class.new(StandardError)
6
6
 
7
7
  it "can wrap original error" do
8
8
  original = HorribleBadThing.new
@@ -14,7 +14,7 @@ describe Cassanity::Error do
14
14
  begin
15
15
  begin
16
16
  raise HorribleBadThing, 'Yep, really bad'
17
- rescue Exception => e
17
+ rescue StandardError => e
18
18
  raise described_class
19
19
  end
20
20
  rescue described_class => e
@@ -28,6 +28,11 @@ describe Cassanity::Error do
28
28
  error.message.should eq('Is this thing on?')
29
29
  end
30
30
 
31
+ it "works with only a string" do
32
+ error = described_class.new("Is this thing on?")
33
+ error.message.should eq('Is this thing on?')
34
+ end
35
+
31
36
  it "does not require any arguments" do
32
37
  error = described_class.new
33
38
  error.message.should eq("Something truly horrible went wrong")
@@ -3,46 +3,50 @@ require 'cassanity/executors/cassandra_cql'
3
3
  require 'cassanity/instrumenters/memory'
4
4
 
5
5
  describe Cassanity::Executors::CassandraCql do
6
- let(:client) { double('Client', :execute => nil) }
6
+ let(:driver) { double('Client', :execute => nil) }
7
7
 
8
8
  let(:required_arguments) {
9
9
  {
10
- client: client,
10
+ driver: driver,
11
11
  }
12
12
  }
13
13
 
14
14
  let(:argument_generators) {
15
15
  {
16
- :foo => lambda { |args| ['mapped', args] },
16
+ :foo => lambda { |*args| ['mapped', *args] },
17
17
  }
18
18
  }
19
19
 
20
20
  let(:result_transformers) {
21
21
  {
22
- :foo => lambda { |args| ['transformed', args] }
22
+ :foo => lambda { |*args| ['transformed', *args] }
23
23
  }
24
24
  }
25
25
 
26
26
  subject { described_class.new(required_arguments) }
27
27
 
28
28
  describe "#initialize" do
29
- [:client].each do |key|
29
+ [:driver].each do |key|
30
30
  it "raises error without :#{key} key" do
31
31
  args = required_arguments.reject { |k, v| k == key }
32
32
  expect { described_class.new(args) }.to raise_error(KeyError)
33
33
  end
34
34
  end
35
35
 
36
- it "sets client" do
37
- subject.client.should eq(client)
36
+ it "sets driver" do
37
+ subject.driver.should eq(driver)
38
+ end
39
+
40
+ it "sets a default retry strategy when none is passed" do
41
+ subject.retry_strategy.should be_an_instance_of(Cassanity::RetryStrategies::RetryNTimes)
38
42
  end
39
43
 
40
44
  it "defaults :argument_generators" do
41
- subject.argument_generators.should eq(described_class::ArgumentGenerators)
45
+ subject.argument_generators.should eq(described_class::DefaultArgumentGenerators)
42
46
  end
43
47
 
44
48
  it "defaults :result_transformers" do
45
- subject.result_transformers.should eq(described_class::ResultTransformers)
49
+ subject.result_transformers.should eq(described_class::DefaultResultTransformers)
46
50
  end
47
51
 
48
52
  it "defaults :instrumenter" do
@@ -90,6 +94,7 @@ describe Cassanity::Executors::CassandraCql do
90
94
  :index_create,
91
95
  :index_drop,
92
96
  :batch,
97
+ :columns,
93
98
  ]
94
99
 
95
100
  KnownCommands.each do |key|
@@ -106,7 +111,7 @@ describe Cassanity::Executors::CassandraCql do
106
111
  }
107
112
 
108
113
  context "for known command" do
109
- it "generates arguments based on command to argument map and passes generated arguments client execute method" do
114
+ it "generates arguments based on command to argument map and passes generated arguments driver execute method" do
110
115
  args = {
111
116
  command: :foo,
112
117
  arguments: {
@@ -114,7 +119,7 @@ describe Cassanity::Executors::CassandraCql do
114
119
  },
115
120
  }
116
121
 
117
- client.should_receive(:execute).with('mapped', args[:arguments])
122
+ driver.should_receive(:execute).with('mapped', args[:arguments])
118
123
  subject.call(args)
119
124
  end
120
125
 
@@ -143,13 +148,60 @@ describe Cassanity::Executors::CassandraCql do
143
148
  event.name.should eq('cql.cassanity')
144
149
  event.payload.should eq({
145
150
  command: :foo,
146
- generator: argument_generators[:foo],
147
- arguments: {something: 'else'},
148
- execute_arguments: ['mapped', {something: 'else'}],
149
- transformer: Cassanity::Executors::CassandraCql::Mirror,
150
151
  result: nil,
152
+ cql: 'mapped',
153
+ cql_variables: [{something: 'else'}],
154
+ attempts: 1,
151
155
  })
152
156
  end
157
+
158
+ context "with retries" do
159
+ let(:retry_strategy) { Cassanity::RetryStrategies::RetryStrategy.new }
160
+ let(:driver) { double('Driver') }
161
+
162
+ subject {
163
+ described_class.new(required_arguments.merge({
164
+ driver: driver,
165
+ argument_generators: argument_generators,
166
+ instrumenter: instrumenter,
167
+ retry_strategy: retry_strategy,
168
+ }))
169
+ }
170
+
171
+ it "adds retry count to the cql.cassanity payload" do
172
+ retry_strategy.should_receive(:fail).once
173
+
174
+ args = {
175
+ command: :foo,
176
+ arguments: {
177
+ something: 'else',
178
+ },
179
+ }
180
+
181
+ i = 0
182
+ driver.stub(:execute) {
183
+ i += 1
184
+ if i > 1
185
+ 'ok!'
186
+ else
187
+ raise cassandra_error('not ok!')
188
+ end
189
+ }
190
+
191
+ subject.call(args)
192
+
193
+ event = instrumenter.events.last
194
+ event.should_not be_nil
195
+ event.name.should eq('cql.cassanity')
196
+ event.payload.should eq({
197
+ command: :foo,
198
+ result: 'ok!',
199
+ cql: 'mapped',
200
+ cql_variables: [{something: 'else'}],
201
+ attempts: 2,
202
+ })
203
+ end
204
+ end
153
205
  end
154
206
 
155
207
  context "with result transformer" do
@@ -162,7 +214,7 @@ describe Cassanity::Executors::CassandraCql do
162
214
 
163
215
  it "returns result transformed" do
164
216
  result = double('Result')
165
- client.stub(:execute => result)
217
+ driver.stub(:execute => result)
166
218
  tranformer = result_transformers[:foo]
167
219
 
168
220
  args = {
@@ -172,7 +224,7 @@ describe Cassanity::Executors::CassandraCql do
172
224
  },
173
225
  }
174
226
 
175
- subject.call(args).should eq(['transformed', result])
227
+ subject.call(args).should eq(['transformed', result, nil])
176
228
  end
177
229
  end
178
230
 
@@ -186,7 +238,7 @@ describe Cassanity::Executors::CassandraCql do
186
238
 
187
239
  it "returns result transformed" do
188
240
  result = double('Result')
189
- client.stub(:execute => result)
241
+ driver.stub(:execute => result)
190
242
  tranformer = result_transformers[:foo]
191
243
 
192
244
  args = {
@@ -202,7 +254,8 @@ describe Cassanity::Executors::CassandraCql do
202
254
  end
203
255
 
204
256
  context "for unknown command" do
205
- it "generates arguments based on command to argument map and passes generated arguments client execute method" do
257
+ it "generates arguments based on command to argument map and passes
258
+ generated arguments driver execute method" do
206
259
  expect {
207
260
  subject.call({
208
261
  command: :surprise,
@@ -211,14 +264,14 @@ describe Cassanity::Executors::CassandraCql do
211
264
  end
212
265
  end
213
266
 
214
- context "when client raises exception" do
267
+ context "when driver raises exception" do
215
268
  it "raises Cassanity::Error" do
216
- client.should_receive(:execute).and_raise(Exception.new)
269
+ driver.should_receive(:execute).and_raise(StandardError.new)
217
270
  expect {
218
271
  subject.call({
219
272
  command: :foo,
220
273
  })
221
- }.to raise_error(Cassanity::Error, /Exception: Exception/)
274
+ }.to raise_error(Cassanity::Error, /StandardError: StandardError/)
222
275
  end
223
276
  end
224
277
  end
@@ -227,7 +280,7 @@ describe Cassanity::Executors::CassandraCql do
227
280
  it "return representation" do
228
281
  result = subject.inspect
229
282
  result.should match(/#{described_class}/)
230
- result.should match(/client=/)
283
+ result.should match(/driver=/)
231
284
  end
232
285
  end
233
286
  end
@@ -2,7 +2,7 @@ require 'helper'
2
2
  require 'cassanity/keyspace'
3
3
 
4
4
  describe Cassanity::Keyspace do
5
- let(:keyspace_name) { 'analytics' }
5
+ let(:keyspace_name) { :analytics }
6
6
 
7
7
  let(:executor) {
8
8
  lambda { |args| ['GOTTA KEEP EM EXECUTED', args] }
@@ -59,8 +59,17 @@ describe Cassanity::Keyspace do
59
59
  end
60
60
  end
61
61
 
62
+ context "with string name" do
63
+ it "converts name to symbol" do
64
+ instance = described_class.new(required_arguments.merge({
65
+ name: 'foo',
66
+ }))
67
+ instance.name.should be(:foo)
68
+ end
69
+ end
70
+
62
71
  describe "#column_family" do
63
- let(:column_family_name) { 'apps' }
72
+ let(:column_family_name) { :apps }
64
73
 
65
74
  context "with only name" do
66
75
  before do
@@ -147,7 +156,7 @@ describe Cassanity::Keyspace do
147
156
  end
148
157
 
149
158
  describe "#table" do
150
- let(:column_family_name) { 'apps' }
159
+ let(:column_family_name) { :apps }
151
160
 
152
161
  before do
153
162
  @return_value = subject.table(column_family_name)
@@ -163,7 +172,7 @@ describe Cassanity::Keyspace do
163
172
  end
164
173
 
165
174
  describe "#[]" do
166
- let(:column_family_name) { 'apps' }
175
+ let(:column_family_name) { :apps }
167
176
 
168
177
  before do
169
178
  @return_value = subject[column_family_name]
@@ -180,21 +189,36 @@ describe Cassanity::Keyspace do
180
189
 
181
190
  shared_examples_for "keyspace existence" do |method_name|
182
191
  it "returns true if name in existing keyspace names" do
183
- executor.should_receive(:call).with(command: :keyspaces).and_return([
184
- {'name' => keyspace_name.to_s},
192
+ executor.should_receive(:call).with({
193
+ command: :keyspaces,
194
+ transformer_arguments: {
195
+ executor: executor,
196
+ },
197
+ }).and_return([
198
+ Cassanity::Keyspace.new({name: keyspace_name, executor: executor}),
185
199
  ])
186
200
  subject.send(method_name).should be_true
187
201
  end
188
202
 
189
203
  it "returns false if name not in existing keyspace names" do
190
- executor.should_receive(:call).with(command: :keyspaces).and_return([
191
- {'name' => 'batman'},
204
+ executor.should_receive(:call).with({
205
+ command: :keyspaces,
206
+ transformer_arguments: {
207
+ executor: executor,
208
+ },
209
+ }).and_return([
210
+ Cassanity::Keyspace.new({name: :batman, executor: executor}),
192
211
  ])
193
212
  subject.send(method_name).should be_false
194
213
  end
195
214
 
196
215
  it "returns false if no keyspaces" do
197
- executor.should_receive(:call).with(command: :keyspaces).and_return([])
216
+ executor.should_receive(:call).with({
217
+ command: :keyspaces,
218
+ transformer_arguments: {
219
+ executor: executor,
220
+ },
221
+ }).and_return([])
198
222
  subject.send(method_name).should be_false
199
223
  end
200
224
  end
@@ -221,10 +245,11 @@ describe Cassanity::Keyspace do
221
245
  describe "#recreate" do
222
246
  context "for existing keyspace" do
223
247
  before do
224
- subject.stub(:exist? => true)
248
+ subject.stub(:exists? => true)
225
249
  end
250
+
226
251
  it "performs drop" do
227
- subject.should_not_receive(:drop)
252
+ subject.should_receive(:drop)
228
253
  subject.recreate
229
254
  end
230
255
 
@@ -236,7 +261,7 @@ describe Cassanity::Keyspace do
236
261
 
237
262
  context "for non-existing keyspace" do
238
263
  before do
239
- subject.stub(:exist? => false)
264
+ subject.stub(:exists? => false)
240
265
  end
241
266
 
242
267
  it "does not perform drop" do
@@ -291,7 +316,7 @@ describe Cassanity::Keyspace do
291
316
 
292
317
  describe "#inspect" do
293
318
  it "return representation" do
294
- subject.inspect.should eq("#<Cassanity::Keyspace:#{subject.object_id} name=\"analytics\">")
319
+ subject.inspect.should eq("#<Cassanity::Keyspace:#{subject.object_id} name=:analytics>")
295
320
  end
296
321
  end
297
322
  end
@@ -0,0 +1,81 @@
1
+ require 'helper'
2
+ require 'cassanity/migration_proxy'
3
+
4
+ describe Cassanity::MigrationProxy do
5
+ describe "#initialize" do
6
+ context "with path that is pathname" do
7
+ it "sets path" do
8
+ instance = described_class.new(Pathname('/some/path/1_foo.rb'))
9
+ instance.path.should eq(Pathname('/some/path/1_foo.rb'))
10
+ end
11
+ end
12
+
13
+ context "with path that is string" do
14
+ it "sets path to pathname" do
15
+ instance = described_class.new('/some/path/1_foo.rb')
16
+ instance.path.should eq(Pathname('/some/path/1_foo.rb'))
17
+ end
18
+ end
19
+
20
+ context "with nil path" do
21
+ it "raises argument error" do
22
+ expect {
23
+ described_class.new(nil)
24
+ }.to raise_error(ArgumentError, 'path cannot be nil')
25
+ end
26
+ end
27
+
28
+ context "with nil name" do
29
+ it "raises argument error" do
30
+ expect {
31
+ described_class.new('/some/1')
32
+ }.to raise_error(ArgumentError, 'name cannot be nil')
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "#eql?" do
38
+ it "returns true for same path" do
39
+ other = described_class.new('/some/path/1_foo.rb')
40
+ described_class.new('/some/path/1_foo.rb').eql?(other).should be_true
41
+ end
42
+
43
+ it "returns false for different path" do
44
+ other = described_class.new('/some/path/1_foo.rb')
45
+ described_class.new('/some/path/2_foo.rb').eql?(other).should be_false
46
+ end
47
+
48
+ it "returns false for different class" do
49
+ other = Object.new
50
+ described_class.new('/some/path/1_foo.rb').eql?(other).should be_false
51
+ end
52
+ end
53
+
54
+ describe "#hash" do
55
+ it "delegates to path" do
56
+ path = '/some/path/1_foo.rb'
57
+ instance = described_class.new(path)
58
+ instance.hash.should eq(path.hash)
59
+ end
60
+ end
61
+
62
+ describe "#<=>" do
63
+ it "returns -1 when version is less than other" do
64
+ older = described_class.new(Pathname('/some/path/1_a.rb'))
65
+ newer = described_class.new(Pathname('/some/path/2_a.rb'))
66
+ (older <=> newer).should be(-1)
67
+ end
68
+
69
+ it "compares against name when version is equal to other" do
70
+ older = described_class.new(Pathname('/some/path/1_a.rb'))
71
+ newer = described_class.new(Pathname('/some/path/2_b.rb'))
72
+ (older <=> newer).should eq(older.name <=> newer.name)
73
+ end
74
+
75
+ it "returns 1 when version is greater than other" do
76
+ older = described_class.new(Pathname('/some/path/1_a.rb'))
77
+ newer = described_class.new(Pathname('/some/path/2_a.rb'))
78
+ (newer <=> older).should be(1)
79
+ end
80
+ end
81
+ end