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