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