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.
- data/.gitignore +2 -1
- data/.travis.yml +1 -0
- data/Gemfile +3 -0
- data/Guardfile +0 -2
- data/README.md +11 -0
- data/doc/Instrumentation.md +40 -0
- data/doc/Migrations.md +132 -0
- data/examples/keyspaces.rb +11 -7
- data/lib/cassanity/argument_generators/column_family_delete.rb +1 -1
- data/lib/cassanity/argument_generators/columns.rb +33 -0
- data/lib/cassanity/argument_generators/where_clause.rb +1 -1
- data/lib/cassanity/client.rb +3 -1
- data/lib/cassanity/column.rb +48 -0
- data/lib/cassanity/column_family.rb +21 -2
- data/lib/cassanity/connection.rb +4 -8
- data/lib/cassanity/error.rb +18 -11
- data/lib/cassanity/executors/cassandra_cql.rb +79 -50
- data/lib/cassanity/instrumentation/log_subscriber.rb +4 -5
- data/lib/cassanity/instrumentation/metriks.rb +6 -0
- data/lib/cassanity/instrumentation/metriks_subscriber.rb +16 -0
- data/lib/cassanity/instrumentation/statsd.rb +6 -0
- data/lib/cassanity/instrumentation/statsd_subscriber.rb +22 -0
- data/lib/cassanity/instrumentation/subscriber.rb +58 -0
- data/lib/cassanity/instrumenters/memory.rb +0 -1
- data/lib/cassanity/keyspace.rb +10 -8
- data/lib/cassanity/migration.rb +125 -0
- data/lib/cassanity/migration_proxy.rb +76 -0
- data/lib/cassanity/migrator.rb +154 -0
- data/lib/cassanity/result_transformers/column_families.rb +20 -0
- data/lib/cassanity/result_transformers/columns.rb +21 -0
- data/lib/cassanity/result_transformers/keyspaces.rb +21 -0
- data/lib/cassanity/result_transformers/mirror.rb +1 -1
- data/lib/cassanity/result_transformers/result_to_array.rb +1 -1
- data/lib/cassanity/retry_strategies/exponential_backoff.rb +43 -0
- data/lib/cassanity/retry_strategies/retry_n_times.rb +29 -0
- data/lib/cassanity/retry_strategies/retry_strategy.rb +35 -0
- data/lib/cassanity/version.rb +1 -1
- data/spec/helper.rb +8 -0
- data/spec/integration/cassanity/column_family_spec.rb +36 -25
- data/spec/integration/cassanity/connection_spec.rb +11 -11
- data/spec/integration/cassanity/fixtures/migrations/20130224135000_create_users.rb +17 -0
- data/spec/integration/cassanity/fixtures/migrations/20130225135002_create_apps.rb +15 -0
- data/spec/integration/cassanity/fixtures/migrations/20130226135004_add_username_to_users.rb +9 -0
- data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +71 -0
- data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +48 -0
- data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +58 -0
- data/spec/integration/cassanity/keyspace_spec.rb +21 -21
- data/spec/integration/cassanity/migration_spec.rb +157 -0
- data/spec/integration/cassanity/migrator_spec.rb +212 -0
- data/spec/support/cassanity_helpers.rb +21 -17
- data/spec/support/fake_udp_socket.rb +27 -0
- data/spec/unit/cassanity/argument_generators/batch_spec.rb +5 -5
- data/spec/unit/cassanity/argument_generators/column_family_delete_spec.rb +20 -6
- data/spec/unit/cassanity/argument_generators/column_family_update_spec.rb +6 -6
- data/spec/unit/cassanity/argument_generators/columns_spec.rb +45 -0
- data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/keyspace_drop_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/keyspace_use_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/where_clause_spec.rb +2 -2
- data/spec/unit/cassanity/client_spec.rb +10 -3
- data/spec/unit/cassanity/column_family_spec.rb +20 -3
- data/spec/unit/cassanity/column_spec.rb +76 -0
- data/spec/unit/cassanity/connection_spec.rb +1 -1
- data/spec/unit/cassanity/error_spec.rb +7 -2
- data/spec/unit/cassanity/executors/cassandra_cql_spec.rb +76 -23
- data/spec/unit/cassanity/keyspace_spec.rb +38 -13
- data/spec/unit/cassanity/migration_proxy_spec.rb +81 -0
- data/spec/unit/cassanity/migration_spec.rb +12 -0
- data/spec/unit/cassanity/migrator_spec.rb +20 -0
- data/spec/unit/cassanity/retry_strategies/exponential_backoff_spec.rb +37 -0
- data/spec/unit/cassanity/retry_strategies/retry_n_times_spec.rb +47 -0
- data/spec/unit/cassanity/retry_strategies/retry_strategy_spec.rb +27 -0
- metadata +56 -4
@@ -2,7 +2,7 @@ require 'helper'
|
|
2
2
|
require 'cassanity/error'
|
3
3
|
|
4
4
|
describe Cassanity::Error do
|
5
|
-
HorribleBadThing = Class.new(
|
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
|
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(:
|
6
|
+
let(:driver) { double('Client', :execute => nil) }
|
7
7
|
|
8
8
|
let(:required_arguments) {
|
9
9
|
{
|
10
|
-
|
10
|
+
driver: driver,
|
11
11
|
}
|
12
12
|
}
|
13
13
|
|
14
14
|
let(:argument_generators) {
|
15
15
|
{
|
16
|
-
:foo => lambda {
|
16
|
+
:foo => lambda { |*args| ['mapped', *args] },
|
17
17
|
}
|
18
18
|
}
|
19
19
|
|
20
20
|
let(:result_transformers) {
|
21
21
|
{
|
22
|
-
:foo => lambda {
|
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
|
-
[:
|
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
|
37
|
-
subject.
|
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::
|
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::
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
267
|
+
context "when driver raises exception" do
|
215
268
|
it "raises Cassanity::Error" do
|
216
|
-
|
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, /
|
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(/
|
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) {
|
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) {
|
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) {
|
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) {
|
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(
|
184
|
-
|
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(
|
191
|
-
|
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(
|
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(:
|
248
|
+
subject.stub(:exists? => true)
|
225
249
|
end
|
250
|
+
|
226
251
|
it "performs drop" do
|
227
|
-
subject.
|
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(:
|
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
|
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
|