cassanity 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/cassanity/connection.rb
CHANGED
@@ -42,16 +42,12 @@ module Cassanity
|
|
42
42
|
#
|
43
43
|
# Returns Array of Cassanity::Keyspace instances.
|
44
44
|
def keyspaces
|
45
|
-
|
45
|
+
@executor.call({
|
46
46
|
command: :keyspaces,
|
47
|
-
|
48
|
-
|
49
|
-
rows.map { |row|
|
50
|
-
Keyspace.new({
|
51
|
-
name: row['name'],
|
47
|
+
transformer_arguments: {
|
52
48
|
executor: @executor,
|
53
|
-
}
|
54
|
-
}
|
49
|
+
},
|
50
|
+
})
|
55
51
|
end
|
56
52
|
|
57
53
|
# Public: Get a keyspace instance
|
data/lib/cassanity/error.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Cassanity
|
2
|
-
class Error <
|
2
|
+
class Error < StandardError
|
3
3
|
# Public: The original error this exception is wrapping.
|
4
4
|
attr_reader :original
|
5
5
|
|
@@ -10,19 +10,26 @@ module Cassanity
|
|
10
10
|
#
|
11
11
|
# Returns the duplicated String.
|
12
12
|
def initialize(args = {})
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
if args.is_a?(String)
|
14
|
+
@message = args
|
15
|
+
else
|
16
|
+
@original = args.fetch(:original) { $! }
|
17
|
+
@message = args.fetch(:message) {
|
18
|
+
if @original
|
19
|
+
"Original Exception: #{@original.class}: #{@original.message}"
|
20
|
+
else
|
21
|
+
"Something truly horrible went wrong"
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
21
25
|
|
22
26
|
super @message
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
# Raised when an argument generator is asked to perform an unknown command.
|
31
|
+
UnknownCommand = Class.new(Error)
|
32
|
+
|
33
|
+
# Raised when a migration operation is attempted that is not supported.
|
34
|
+
MigrationOperationNotSupported = Class.new(Error)
|
28
35
|
end
|
@@ -18,8 +18,14 @@ require 'cassanity/argument_generators/column_family_alter'
|
|
18
18
|
require 'cassanity/argument_generators/index_create'
|
19
19
|
require 'cassanity/argument_generators/index_drop'
|
20
20
|
require 'cassanity/argument_generators/batch'
|
21
|
+
require 'cassanity/argument_generators/columns'
|
21
22
|
require 'cassanity/result_transformers/result_to_array'
|
23
|
+
require 'cassanity/result_transformers/keyspaces'
|
24
|
+
require 'cassanity/result_transformers/column_families'
|
25
|
+
require 'cassanity/result_transformers/columns'
|
22
26
|
require 'cassanity/result_transformers/mirror'
|
27
|
+
require 'cassanity/retry_strategies/retry_n_times'
|
28
|
+
require 'cassanity/retry_strategies/exponential_backoff'
|
23
29
|
|
24
30
|
module Cassanity
|
25
31
|
module Executors
|
@@ -27,40 +33,45 @@ module Cassanity
|
|
27
33
|
extend Forwardable
|
28
34
|
|
29
35
|
# Private: Hash of commands to related argument generators.
|
30
|
-
|
31
|
-
keyspaces:
|
32
|
-
keyspace_create:
|
33
|
-
keyspace_drop:
|
34
|
-
keyspace_use:
|
35
|
-
column_families:
|
36
|
-
column_family_create:
|
37
|
-
column_family_drop:
|
38
|
-
column_family_truncate:
|
39
|
-
column_family_select:
|
40
|
-
column_family_insert:
|
41
|
-
column_family_update:
|
42
|
-
column_family_delete:
|
43
|
-
column_family_alter:
|
44
|
-
index_create:
|
45
|
-
index_drop:
|
46
|
-
batch:
|
36
|
+
DefaultArgumentGenerators = {
|
37
|
+
keyspaces: ArgumentGenerators::Keyspaces.new,
|
38
|
+
keyspace_create: ArgumentGenerators::KeyspaceCreate.new,
|
39
|
+
keyspace_drop: ArgumentGenerators::KeyspaceDrop.new,
|
40
|
+
keyspace_use: ArgumentGenerators::KeyspaceUse.new,
|
41
|
+
column_families: ArgumentGenerators::ColumnFamilies.new,
|
42
|
+
column_family_create: ArgumentGenerators::ColumnFamilyCreate.new,
|
43
|
+
column_family_drop: ArgumentGenerators::ColumnFamilyDrop.new,
|
44
|
+
column_family_truncate: ArgumentGenerators::ColumnFamilyTruncate.new,
|
45
|
+
column_family_select: ArgumentGenerators::ColumnFamilySelect.new,
|
46
|
+
column_family_insert: ArgumentGenerators::ColumnFamilyInsert.new,
|
47
|
+
column_family_update: ArgumentGenerators::ColumnFamilyUpdate.new,
|
48
|
+
column_family_delete: ArgumentGenerators::ColumnFamilyDelete.new,
|
49
|
+
column_family_alter: ArgumentGenerators::ColumnFamilyAlter.new,
|
50
|
+
index_create: ArgumentGenerators::IndexCreate.new,
|
51
|
+
index_drop: ArgumentGenerators::IndexDrop.new,
|
52
|
+
batch: ArgumentGenerators::Batch.new,
|
53
|
+
columns: ArgumentGenerators::Columns.new,
|
47
54
|
}
|
48
55
|
|
49
56
|
# Private: Hash of commands to related result transformers.
|
50
|
-
|
51
|
-
keyspaces:
|
52
|
-
column_families:
|
53
|
-
column_family_select:
|
57
|
+
DefaultResultTransformers = {
|
58
|
+
keyspaces: ResultTransformers::Keyspaces.new,
|
59
|
+
column_families: ResultTransformers::ColumnFamilies.new,
|
60
|
+
column_family_select: ResultTransformers::ResultToArray.new,
|
61
|
+
columns: ResultTransformers::Columns.new,
|
54
62
|
}
|
55
63
|
|
64
|
+
# Private: Default retry strategy to retry N times.
|
65
|
+
DefaultRetryStrategy = RetryStrategies::RetryNTimes.new
|
66
|
+
|
56
67
|
# Private: Default result transformer for commands that do not have one.
|
57
|
-
Mirror =
|
68
|
+
Mirror = ResultTransformers::Mirror.new
|
58
69
|
|
59
70
|
# Private: Forward #instrument to instrumenter.
|
60
71
|
def_delegator :@instrumenter, :instrument
|
61
72
|
|
62
73
|
# Private
|
63
|
-
attr_reader :
|
74
|
+
attr_reader :driver
|
64
75
|
|
65
76
|
# Private
|
66
77
|
attr_reader :argument_generators
|
@@ -71,10 +82,13 @@ module Cassanity
|
|
71
82
|
# Private: What should be used to instrument all the things.
|
72
83
|
attr_reader :instrumenter
|
73
84
|
|
85
|
+
# Private: What strategy to use when retrying Cassandra commands
|
86
|
+
attr_reader :retry_strategy
|
87
|
+
|
74
88
|
# Internal: Initializes a cassandra-cql based CQL executor.
|
75
89
|
#
|
76
90
|
# args - The Hash of arguments.
|
77
|
-
# :
|
91
|
+
# :driver - The CassandraCQL::Database connection instance.
|
78
92
|
# :instrumenter - What should be used to instrument all the things
|
79
93
|
# (default: Cassanity::Instrumenters::Noop).
|
80
94
|
# :argument_generators - A Hash where each key is a command name
|
@@ -85,17 +99,21 @@ module Cassanity
|
|
85
99
|
# and each value is the related result
|
86
100
|
# transformer that responds to `call`
|
87
101
|
# (optional).
|
102
|
+
# :retry_strategy - What retry strategy to use on failed
|
103
|
+
# CassandraCQL calls
|
104
|
+
# (default: Cassanity::Instrumenters::RetryNTimes)
|
88
105
|
#
|
89
106
|
# Examples
|
90
107
|
#
|
91
|
-
#
|
92
|
-
# Cassanity::Executors::CassandraCql.new(
|
108
|
+
# driver = CassandraCQL::Database.new('host', cql_version: '3.0.0')
|
109
|
+
# Cassanity::Executors::CassandraCql.new(driver: driver)
|
93
110
|
#
|
94
111
|
def initialize(args = {})
|
95
|
-
@
|
112
|
+
@driver = args.fetch(:driver)
|
96
113
|
@instrumenter = args[:instrumenter] || Instrumenters::Noop
|
97
|
-
@argument_generators = args.fetch(:argument_generators)
|
98
|
-
@result_transformers = args.fetch(:result_transformers)
|
114
|
+
@argument_generators = args.fetch(:argument_generators, DefaultArgumentGenerators)
|
115
|
+
@result_transformers = args.fetch(:result_transformers, DefaultResultTransformers)
|
116
|
+
@retry_strategy = args[:retry_strategy] || DefaultRetryStrategy
|
99
117
|
end
|
100
118
|
|
101
119
|
# Internal: Execute a CQL query.
|
@@ -112,42 +130,53 @@ module Cassanity
|
|
112
130
|
#
|
113
131
|
# call({
|
114
132
|
# command: :keyspace_create,
|
115
|
-
# arguments: {
|
133
|
+
# arguments: {keyspace_name: 'analytics'},
|
116
134
|
# })
|
117
135
|
#
|
118
136
|
# Returns the result of execution.
|
119
137
|
# Raises Cassanity::Error if anything goes wrong during execution.
|
120
138
|
def call(args = {})
|
121
|
-
instrument('cql.cassanity'
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
139
|
+
instrument('cql.cassanity') do |payload|
|
140
|
+
begin
|
141
|
+
command = args.fetch(:command)
|
142
|
+
payload[:command] = command
|
143
|
+
generator = @argument_generators.fetch(command)
|
144
|
+
rescue KeyError => e
|
145
|
+
raise Cassanity::UnknownCommand
|
146
|
+
end
|
126
147
|
|
127
|
-
|
128
|
-
|
129
|
-
transformer = @result_transformers.fetch(command) { Mirror }
|
130
|
-
transformed_result = transformer.call(result)
|
148
|
+
arguments = args[:arguments]
|
131
149
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
150
|
+
if arguments
|
151
|
+
if (keyspace_name = arguments[:keyspace_name])
|
152
|
+
payload[:keyspace_name] = keyspace_name
|
153
|
+
end
|
154
|
+
|
155
|
+
if (column_family_name = arguments[:column_family_name])
|
156
|
+
payload[:column_family_name] = column_family_name
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
begin
|
161
|
+
execute_arguments = generator.call(arguments)
|
162
|
+
payload[:cql] = execute_arguments[0]
|
163
|
+
payload[:cql_variables] = execute_arguments[1..-1]
|
164
|
+
result = @retry_strategy.execute(payload) { @driver.execute(*execute_arguments) }
|
165
|
+
transformer = @result_transformers.fetch(command, Mirror)
|
166
|
+
transformed_result = transformer.call(result, args[:transformer_arguments])
|
167
|
+
payload[:result] = transformed_result
|
168
|
+
rescue StandardError => e
|
169
|
+
raise Cassanity::Error
|
170
|
+
end
|
138
171
|
|
139
172
|
transformed_result
|
140
173
|
end
|
141
|
-
rescue KeyError
|
142
|
-
raise Cassanity::UnknownCommand
|
143
|
-
rescue Exception => e
|
144
|
-
raise Cassanity::Error
|
145
174
|
end
|
146
175
|
|
147
176
|
# Public
|
148
177
|
def inspect
|
149
178
|
attributes = [
|
150
|
-
"
|
179
|
+
"driver=#{@driver.inspect}",
|
151
180
|
]
|
152
181
|
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
153
182
|
end
|
@@ -10,13 +10,12 @@ module Cassanity
|
|
10
10
|
|
11
11
|
name = '%s (%.1fms)' % ["CQL Query", event.duration]
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
arguments = args.map { |arg| arg.inspect }.join(', ')
|
13
|
+
cql = event.payload[:cql]
|
14
|
+
vars = event.payload[:cql_variables] || []
|
15
|
+
variables = vars.map { |var| var.inspect }.join(', ')
|
17
16
|
|
18
17
|
query = "#{cql}"
|
19
|
-
query += " (#{
|
18
|
+
query += " (#{variables})" unless variables.empty?
|
20
19
|
|
21
20
|
debug " #{color(name, CYAN, true)} [ #{query} ]"
|
22
21
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Note: You should never need to require this file directly if you are using
|
2
|
+
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
|
+
# that lives in the same directory as this file. The benefit is that it
|
4
|
+
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'cassanity/instrumentation/subscriber'
|
6
|
+
require 'metriks'
|
7
|
+
|
8
|
+
module Cassanity
|
9
|
+
module Instrumentation
|
10
|
+
class MetriksSubscriber < Subscriber
|
11
|
+
def update_timer(metric)
|
12
|
+
Metriks.timer(metric).update(@duration)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Note: You should never need to require this file directly if you are using
|
2
|
+
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
|
+
# that lives in the same directory as this file. The benefit is that it
|
4
|
+
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'cassanity/instrumentation/subscriber'
|
6
|
+
require 'statsd'
|
7
|
+
|
8
|
+
module Cassanity
|
9
|
+
module Instrumentation
|
10
|
+
class StatsdSubscriber < Subscriber
|
11
|
+
class << self
|
12
|
+
attr_accessor :client
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_timer(metric)
|
16
|
+
if self.class.client
|
17
|
+
self.class.client.timing metric, (@duration * 1_000).round
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Cassanity
|
2
|
+
module Instrumentation
|
3
|
+
class Subscriber
|
4
|
+
# Public: Use this as the subscribed block.
|
5
|
+
def self.call(name, start, ending, transaction_id, payload)
|
6
|
+
new(name, start, ending, transaction_id, payload).update
|
7
|
+
end
|
8
|
+
|
9
|
+
# Private: Initializes a new event processing instance.
|
10
|
+
def initialize(name, start, ending, transaction_id, payload)
|
11
|
+
@name = name
|
12
|
+
@start = start
|
13
|
+
@ending = ending
|
14
|
+
@payload = payload
|
15
|
+
@duration = ending - start
|
16
|
+
@transaction_id = transaction_id
|
17
|
+
|
18
|
+
@command_name = @payload[:command]
|
19
|
+
@keyspace_name = @payload[:keyspace_name]
|
20
|
+
@column_family_name = @payload[:column_family_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Actually update all the metriks timers for the event.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def update
|
27
|
+
update_timer 'cassanity.cql'
|
28
|
+
|
29
|
+
if command_name?
|
30
|
+
update_timer "cassanity.command.#{@command_name}.cql"
|
31
|
+
end
|
32
|
+
|
33
|
+
if column_family_name?
|
34
|
+
update_timer "cassanity.column_family.#{@column_family_name}.cql"
|
35
|
+
end
|
36
|
+
|
37
|
+
if column_family_name? && command_name?
|
38
|
+
update_timer "cassanity.column_family.#{@column_family_name}.#{@command_name}.cql"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Override in subclass.
|
43
|
+
def update_timer(metric)
|
44
|
+
raise 'not implemented'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Private: Returns true if command name present else false.
|
48
|
+
def command_name?
|
49
|
+
@command_name_present ||= !@command_name.nil? && !@command_name.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Private: Returns true if column family name present else false.
|
53
|
+
def column_family_name?
|
54
|
+
@column_family_name_present ||= !@column_family_name.nil? && !@column_family_name.empty?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/cassanity/keyspace.rb
CHANGED
@@ -25,7 +25,7 @@ module Cassanity
|
|
25
25
|
# creating keyspace.
|
26
26
|
#
|
27
27
|
def initialize(args = {})
|
28
|
-
@name = args.fetch(:name)
|
28
|
+
@name = args.fetch(:name).to_sym
|
29
29
|
@executor = args.fetch(:executor)
|
30
30
|
@strategy_class = args[:strategy_class]
|
31
31
|
@strategy_options = args[:strategy_options]
|
@@ -36,8 +36,12 @@ module Cassanity
|
|
36
36
|
#
|
37
37
|
# Returns true if keyspace exists, false if it does not.
|
38
38
|
def exists?
|
39
|
-
|
40
|
-
|
39
|
+
@executor.call({
|
40
|
+
command: :keyspaces,
|
41
|
+
transformer_arguments: {
|
42
|
+
executor: @executor,
|
43
|
+
},
|
44
|
+
}).any? { |keyspace| keyspace.name == @name }
|
41
45
|
end
|
42
46
|
|
43
47
|
alias_method :exist?, :exists?
|
@@ -125,12 +129,10 @@ module Cassanity
|
|
125
129
|
arguments: {
|
126
130
|
keyspace_name: @name,
|
127
131
|
},
|
128
|
-
|
129
|
-
ColumnFamily.new({
|
130
|
-
name: row['columnfamily'],
|
132
|
+
transformer_arguments: {
|
131
133
|
keyspace: self,
|
132
|
-
}
|
133
|
-
}
|
134
|
+
}
|
135
|
+
})
|
134
136
|
end
|
135
137
|
|
136
138
|
# Public: Get a column family instance
|