right_support 2.8.12 → 2.8.15
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/.rspec +0 -1
- data/Gemfile +15 -21
- data/Gemfile.lock +59 -83
- data/Rakefile +0 -4
- data/VERSION +1 -1
- data/features/hash_tools.feature +27 -0
- data/features/step_definitions/hash_tools_steps.rb +41 -0
- data/lib/right_support/db/cassandra_model.rb +123 -109
- data/lib/right_support/log/mixin.rb +9 -2
- data/lib/right_support/rack/request_logger.rb +41 -28
- data/right_support.gemspec +14 -31
- data/right_support.rconf +4 -2
- data/spec/db/cassandra_model_part1_spec.rb +1 -1
- data/spec/db/cassandra_model_spec.rb +2 -2
- data/spec/log/mixin_spec.rb +29 -0
- data/spec/net/request_balancer_spec.rb +0 -1
- data/spec/rack/request_logger_spec.rb +39 -0
- data/spec/spec_helper.rb +0 -3
- metadata +36 -106
- data/features/continuous_integration.feature +0 -51
- data/features/continuous_integration_cucumber.feature +0 -28
- data/features/continuous_integration_rspec1.feature +0 -28
- data/features/continuous_integration_rspec2.feature +0 -28
data/.rspec
CHANGED
data/Gemfile
CHANGED
@@ -1,30 +1,24 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Gems that RightSupport can optionally make use of, but which it does
|
4
|
-
# not require to be installed. These would be
|
4
|
+
# not require to be installed. These would be 'optional dependencies'
|
5
5
|
# if gemspecs allowed for them.
|
6
6
|
group :optional do
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
7
|
+
gem 'net-ssh', '~> 2.0'
|
8
|
+
gem 'mime-types', '~> 1.0' # mime-types 2.x is no longer compatible with Ruby 1.8 and mime-types 1.x is only being maintained for security issues
|
9
|
+
gem 'rest-client', '~> 1.6'
|
10
|
+
gem 'addressable', '~> 2.2.7'
|
11
|
+
gem 'uuidtools', '~> 2.0', :require => nil
|
12
|
+
gem 'simple_uuid', '~> 0.2', :require => nil
|
13
|
+
gem 'uuid', '~> 2.3', :require => nil
|
14
|
+
gem 'yajl-ruby', '~> 1.1'
|
15
|
+
gem 'iconv', :platforms => :ruby_18
|
15
16
|
end
|
16
17
|
|
17
18
|
# Gems used during test and development of RightSupport.
|
18
19
|
group :development do
|
19
|
-
gem
|
20
|
-
gem
|
21
|
-
gem
|
22
|
-
|
23
|
-
:branch => "master"
|
24
|
-
gem "ruby-debug", ">= 0.10", :platforms => :ruby_18
|
25
|
-
gem "ruby-debug19", ">= 0.11.6", :platforms => :ruby_19
|
26
|
-
gem "rdoc", ">= 2.4.2"
|
27
|
-
gem "flexmock", "~> 1.0"
|
28
|
-
gem "syntax", "~> 1.0.0" #rspec will syntax-highlight code snippets if this gem is available
|
29
|
-
gem "nokogiri", "~> 1.5"
|
20
|
+
gem 'jeweler', '~> 2.0'
|
21
|
+
gem 'flexmock', '~> 1.0'
|
22
|
+
gem 'rspec', '~> 2.13.0'
|
23
|
+
gem 'cucumber', ['~> 1.0', '< 1.3.3'] # Cuke >= 1.3.3 depends on RubyGems > 2.0 without specifying that in its gemspec
|
30
24
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,115 +1,91 @@
|
|
1
|
-
GIT
|
2
|
-
remote: git@github.com:rightscale/right_develop.git
|
3
|
-
revision: dedba69a68e8b56cc88db7d6dc518d42747b0586
|
4
|
-
branch: master
|
5
|
-
specs:
|
6
|
-
right_develop (1.2.2)
|
7
|
-
actionpack (>= 2.3.0, < 4.0)
|
8
|
-
builder (~> 3.0)
|
9
|
-
cucumber (~> 1.0)
|
10
|
-
rake (>= 0.8.7, < 0.10)
|
11
|
-
right_support (~> 2.0)
|
12
|
-
rspec (>= 1.3, < 3.0)
|
13
|
-
trollop (>= 1.0, < 3.0)
|
14
|
-
|
15
1
|
GEM
|
16
|
-
remote:
|
2
|
+
remote: https://rubygems.org/
|
17
3
|
specs:
|
18
|
-
actionpack (2.3.18)
|
19
|
-
activesupport (= 2.3.18)
|
20
|
-
rack (~> 1.1.0)
|
21
|
-
activesupport (2.3.18)
|
22
4
|
addressable (2.2.8)
|
23
|
-
archive-tar-minitar (0.5.2)
|
24
5
|
builder (3.2.2)
|
25
|
-
|
26
|
-
cucumber (1.3.3)
|
6
|
+
cucumber (1.3.2)
|
27
7
|
builder (>= 2.1.2)
|
28
8
|
diff-lcs (>= 1.1.3)
|
29
9
|
gherkin (~> 2.12.0)
|
30
|
-
multi_json (~> 1.7.5)
|
31
|
-
multi_test (~> 0.0.1)
|
32
|
-
diff-lcs (1.2.4)
|
33
|
-
flexmock (1.3.2)
|
34
|
-
gherkin (2.12.0)
|
35
10
|
multi_json (~> 1.3)
|
36
|
-
|
11
|
+
diff-lcs (1.2.5)
|
12
|
+
faraday (0.8.9)
|
13
|
+
multipart-post (~> 1.2.0)
|
14
|
+
flexmock (1.3.3)
|
15
|
+
gherkin (2.12.2)
|
16
|
+
multi_json (~> 1.3)
|
17
|
+
git (1.2.6)
|
18
|
+
github_api (0.10.1)
|
19
|
+
addressable
|
20
|
+
faraday (~> 0.8.1)
|
21
|
+
hashie (>= 1.2)
|
22
|
+
multi_json (~> 1.4)
|
23
|
+
nokogiri (~> 1.5.2)
|
24
|
+
oauth2
|
25
|
+
hashie (2.0.5)
|
26
|
+
highline (1.6.20)
|
37
27
|
iconv (1.0.4)
|
38
|
-
jeweler (
|
39
|
-
|
28
|
+
jeweler (2.0.1)
|
29
|
+
builder
|
30
|
+
bundler (>= 1.0)
|
40
31
|
git (>= 1.2.5)
|
32
|
+
github_api
|
33
|
+
highline (>= 1.6.15)
|
34
|
+
nokogiri (>= 1.5.10)
|
41
35
|
rake
|
42
36
|
rdoc
|
43
|
-
json (1.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
37
|
+
json (1.8.1)
|
38
|
+
jwt (0.1.11)
|
39
|
+
multi_json (>= 1.5)
|
40
|
+
macaddr (1.6.5)
|
41
|
+
systemu (~> 2.6.2)
|
42
|
+
mime-types (1.25.1)
|
43
|
+
multi_json (1.8.4)
|
44
|
+
multi_xml (0.5.5)
|
45
|
+
multipart-post (1.2.0)
|
46
|
+
net-ssh (2.8.0)
|
47
|
+
nokogiri (1.5.10)
|
48
|
+
oauth2 (0.9.3)
|
49
|
+
faraday (>= 0.8, < 0.10)
|
50
|
+
jwt (~> 0.1.8)
|
51
|
+
multi_json (~> 1.3)
|
52
|
+
multi_xml (~> 0.5)
|
53
|
+
rack (~> 1.2)
|
54
|
+
rack (1.5.2)
|
55
|
+
rake (10.1.1)
|
56
|
+
rdoc (4.1.1)
|
59
57
|
json (~> 1.4)
|
60
58
|
rest-client (1.6.7)
|
61
59
|
mime-types (>= 1.16)
|
62
|
-
|
63
|
-
|
64
|
-
rspec-
|
65
|
-
rspec-
|
66
|
-
|
67
|
-
rspec-
|
68
|
-
rspec-expectations (2.14.0)
|
60
|
+
rspec (2.13.0)
|
61
|
+
rspec-core (~> 2.13.0)
|
62
|
+
rspec-expectations (~> 2.13.0)
|
63
|
+
rspec-mocks (~> 2.13.0)
|
64
|
+
rspec-core (2.13.1)
|
65
|
+
rspec-expectations (2.13.0)
|
69
66
|
diff-lcs (>= 1.1.3, < 2.0)
|
70
|
-
rspec-mocks (2.
|
71
|
-
|
72
|
-
|
73
|
-
ruby-debug-base (~> 0.10.4.0)
|
74
|
-
ruby-debug-base (0.10.4)
|
75
|
-
linecache (>= 0.3)
|
76
|
-
ruby-debug-base19 (0.11.25)
|
77
|
-
columnize (>= 0.3.1)
|
78
|
-
linecache19 (>= 0.5.11)
|
79
|
-
ruby_core_source (>= 0.1.4)
|
80
|
-
ruby-debug19 (0.11.6)
|
81
|
-
columnize (>= 0.3.1)
|
82
|
-
linecache19 (>= 0.5.11)
|
83
|
-
ruby-debug-base19 (>= 0.11.19)
|
84
|
-
ruby_core_source (0.1.5)
|
85
|
-
archive-tar-minitar (>= 0.5.2)
|
86
|
-
simple_uuid (0.3.0)
|
87
|
-
syntax (1.0.0)
|
88
|
-
systemu (2.5.2)
|
89
|
-
trollop (2.0)
|
67
|
+
rspec-mocks (2.13.1)
|
68
|
+
simple_uuid (0.4.0)
|
69
|
+
systemu (2.6.3)
|
90
70
|
uuid (2.3.7)
|
91
71
|
macaddr (~> 1.0)
|
92
|
-
uuidtools (2.1.
|
93
|
-
yajl-ruby (1.
|
72
|
+
uuidtools (2.1.4)
|
73
|
+
yajl-ruby (1.2.0)
|
94
74
|
|
95
75
|
PLATFORMS
|
96
76
|
ruby
|
97
77
|
|
98
78
|
DEPENDENCIES
|
99
79
|
addressable (~> 2.2.7)
|
80
|
+
cucumber (~> 1.0, < 1.3.3)
|
100
81
|
flexmock (~> 1.0)
|
101
82
|
iconv
|
102
|
-
jeweler (~>
|
83
|
+
jeweler (~> 2.0)
|
84
|
+
mime-types (~> 1.0)
|
103
85
|
net-ssh (~> 2.0)
|
104
|
-
nokogiri (~> 1.5)
|
105
|
-
rake (~> 0.9)
|
106
|
-
rdoc (>= 2.4.2)
|
107
86
|
rest-client (~> 1.6)
|
108
|
-
|
109
|
-
ruby-debug (>= 0.10)
|
110
|
-
ruby-debug19 (>= 0.11.6)
|
87
|
+
rspec (~> 2.13.0)
|
111
88
|
simple_uuid (~> 0.2)
|
112
|
-
syntax (~> 1.0.0)
|
113
89
|
uuid (~> 2.3)
|
114
90
|
uuidtools (~> 2.0)
|
115
91
|
yajl-ruby (~> 1.1)
|
data/Rakefile
CHANGED
@@ -10,8 +10,6 @@ require 'rake/clean'
|
|
10
10
|
require 'rspec/core/rake_task'
|
11
11
|
require 'cucumber/rake/task'
|
12
12
|
|
13
|
-
require 'right_develop/ci/rake_task'
|
14
|
-
|
15
13
|
# But, we have a very special need, because OUR Cucumbers need to run with a pristine
|
16
14
|
# environment that isn't polluted by RVM or RubyGems or anyone else, in order to validate
|
17
15
|
# that RightSupport's CI harness doesn't break your app if those gems are unavailable.
|
@@ -63,5 +61,3 @@ tasks.jeweler.commit = false
|
|
63
61
|
Jeweler::RubygemsDotOrgTasks.new
|
64
62
|
|
65
63
|
CLEAN.include('pkg')
|
66
|
-
|
67
|
-
RightDevelop::CI::RakeTask.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.8.
|
1
|
+
2.8.15
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: tools for hash manipulation
|
2
|
+
In order to successfully work with hashes
|
3
|
+
RightSupport should have a tested set of tools
|
4
|
+
So the developers can trust each method
|
5
|
+
|
6
|
+
Scenario: getting a value from a path
|
7
|
+
Given a hash map with the form {"key1":"val1", "key2":{"key3":"val2"}}
|
8
|
+
And a path array ["key2", "key3"]
|
9
|
+
When I call the deep_get method
|
10
|
+
Then The deep_get method response should be: val2
|
11
|
+
|
12
|
+
Scenario: setting a value on a path
|
13
|
+
Given a hash map with the form {"key1":{"key4":{"key5":"no"}}, "key2":{"key3":"val2"}}
|
14
|
+
And a path array ["key1", "key4", "key5"]
|
15
|
+
And a value val3
|
16
|
+
When I call the deep_set! method
|
17
|
+
Then The hash should be: {"key1":{"key4":{"key5":"val3"}}, "key2":{"key3":"val2"}}
|
18
|
+
|
19
|
+
Scenario: merging two hashes
|
20
|
+
Given a hash map with the form {"key1":"val1", "key2":{"key3":"val2"}}
|
21
|
+
When I call the deep_merge! method with the source: {"key1":{"key4":"val3"}}
|
22
|
+
Then The hash should be: {"key1":{"key4":"val3"}, "key2":{"key3":"val2"}}
|
23
|
+
|
24
|
+
Scenario: subtracting a hash
|
25
|
+
Given a hash map with the form {"key1":"val1", "key2":{"key3":"val2"}}
|
26
|
+
When I call the deep_remove! method with the source: {"key1":"val1"}
|
27
|
+
Then The hash should be: {"key2":{"key3":"val2"}}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
Given /^a hash map with the form (.*)$/ do |hash|
|
4
|
+
@hash = JSON.parse(hash)
|
5
|
+
end
|
6
|
+
|
7
|
+
Given /^a path array (.*)$/ do |path|
|
8
|
+
@path = JSON.parse(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^a value (.*)$/ do |value|
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I call the deep_get method$/ do
|
16
|
+
@deep_get_value = RightSupport::Data::HashTools.deep_get(@hash, @path)
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I call the deep_set! method$/ do
|
20
|
+
RightSupport::Data::HashTools.deep_set!(@hash, @path, @value)
|
21
|
+
end
|
22
|
+
|
23
|
+
When /^I call the deep_merge! method with the source: (.*)$/ do |source|
|
24
|
+
RightSupport::Data::HashTools.deep_merge!(@hash, JSON.parse(source))
|
25
|
+
end
|
26
|
+
|
27
|
+
When /^I call the deep_remove! method with the source: (.*)$/ do |source|
|
28
|
+
RightSupport::Data::HashTools.deep_remove!(@hash, JSON.parse(source))
|
29
|
+
end
|
30
|
+
|
31
|
+
Then /^The hash should be: (.*)$/ do |hash|
|
32
|
+
@hash == JSON.parse(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^The deep_get method response should be: (.*)$/ do |expression|
|
36
|
+
@deep_get_value == expression
|
37
|
+
end
|
38
|
+
|
39
|
+
Then /^The generate method response should be: (.*)$/ do |expression|
|
40
|
+
@generated_value == expression
|
41
|
+
end
|
@@ -34,11 +34,11 @@ if require_succeeds?('cassandra/0.8')
|
|
34
34
|
predicate = CassandraThrift::SlicePredicate.new(:column_names => [columns].flatten)
|
35
35
|
else
|
36
36
|
predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
CassandraThrift::SliceRange.new(
|
38
|
+
:reversed => reversed,
|
39
|
+
:count => count,
|
40
|
+
:start => start,
|
41
|
+
:finish => finish))
|
42
42
|
end
|
43
43
|
client.get_indexed_slices(column_parent, index_clause, predicate, consistency)
|
44
44
|
end
|
@@ -48,7 +48,7 @@ if require_succeeds?('cassandra/0.8')
|
|
48
48
|
# rather than converting it to a Hash
|
49
49
|
def get_range_single(column_family, options = {})
|
50
50
|
return_empty_rows = options.delete(:return_empty_rows) || false
|
51
|
-
slices_please
|
51
|
+
slices_please = options.delete(:slices_not_hash) || false
|
52
52
|
|
53
53
|
column_family, _, _, options =
|
54
54
|
extract_and_validate_params(column_family, "", [options],
|
@@ -57,19 +57,19 @@ if require_succeeds?('cassandra/0.8')
|
|
57
57
|
:key_count => 100,
|
58
58
|
:columns => nil,
|
59
59
|
:reversed => false
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
results = _get_range(
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
60
|
+
)
|
61
|
+
)
|
62
|
+
|
63
|
+
results = _get_range(column_family,
|
64
|
+
options[:start_key].to_s,
|
65
|
+
options[:finish_key].to_s,
|
66
|
+
options[:key_count],
|
67
|
+
options[:columns],
|
68
|
+
options[:start].to_s,
|
69
|
+
options[:finish].to_s,
|
70
|
+
options[:count],
|
71
|
+
options[:consistency],
|
72
|
+
options[:reversed])
|
73
73
|
|
74
74
|
unless slices_please
|
75
75
|
multi_key_slices_to_hash(column_family, results, return_empty_rows)
|
@@ -95,9 +95,9 @@ if require_succeeds?('cassandra/0.8')
|
|
95
95
|
end
|
96
96
|
|
97
97
|
key_slices = _get_indexed_slices(column_family, index_clause, columns, options[:count], options[:start],
|
98
|
-
|
98
|
+
options[:finish], options[:reversed], options[:consistency])
|
99
99
|
|
100
|
-
key_slices.inject(OrderedHash.new){|h, key_slice| h[key_slice.key] = key_slice.columns; h}
|
100
|
+
key_slices.inject(OrderedHash.new) { |h, key_slice| h[key_slice.key] = key_slice.columns; h }
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -110,12 +110,12 @@ if (RUBY_PLATFORM =~ /java/) && require_succeeds?('thrift')
|
|
110
110
|
def open
|
111
111
|
begin
|
112
112
|
addrinfo = ::Socket::getaddrinfo(@host, @port).first
|
113
|
-
@handle
|
113
|
+
@handle = ::Socket.new(addrinfo[4], ::Socket::SOCK_STREAM, 0)
|
114
114
|
sockaddr = ::Socket.sockaddr_in(addrinfo[1], addrinfo[3])
|
115
115
|
begin
|
116
116
|
@handle.connect_nonblock(sockaddr)
|
117
117
|
rescue Errno::EINPROGRESS
|
118
|
-
resp = IO.select(nil, [
|
118
|
+
resp = IO.select(nil, [@handle], nil, @timeout) # 3 lines removed here, 1 line added
|
119
119
|
begin
|
120
120
|
@handle.connect_nonblock(sockaddr)
|
121
121
|
rescue Errno::EISCONN
|
@@ -132,8 +132,10 @@ end
|
|
132
132
|
|
133
133
|
module RightSupport::DB
|
134
134
|
# Exception that indicates database configuration info is missing.
|
135
|
-
class MissingConfiguration < Exception;
|
136
|
-
|
135
|
+
class MissingConfiguration < Exception;
|
136
|
+
end
|
137
|
+
class UnsupportedRubyVersion < Exception;
|
138
|
+
end
|
137
139
|
# Base class for a column family in a keyspace
|
138
140
|
# Used to access data persisted in Cassandra
|
139
141
|
# Provides wrappers for Cassandra client methods
|
@@ -144,12 +146,12 @@ module RightSupport::DB
|
|
144
146
|
DEFAULT_TIMEOUT = 20
|
145
147
|
|
146
148
|
# Default maximum number of rows to retrieve in one chunk
|
147
|
-
DEFAULT_COUNT
|
149
|
+
DEFAULT_COUNT = 100
|
148
150
|
|
149
|
-
|
151
|
+
# Wrappers for Cassandra client
|
150
152
|
class << self
|
151
153
|
|
152
|
-
attr_reader
|
154
|
+
attr_reader :default_keyspace
|
153
155
|
attr_accessor :column_family
|
154
156
|
|
155
157
|
@@current_keyspace = nil
|
@@ -242,8 +244,8 @@ module RightSupport::DB
|
|
242
244
|
# keyspace(String):: Set the default keyspace
|
243
245
|
|
244
246
|
def keyspace=(kyspc)
|
245
|
-
env
|
246
|
-
nspace
|
247
|
+
env = ENV['RACK_ENV'] || 'development'
|
248
|
+
nspace = namespace(env)
|
247
249
|
@@default_keyspace = "#{kyspc}_#{env}"
|
248
250
|
@@default_keyspace += "_#{nspace}" if nspace
|
249
251
|
end
|
@@ -258,8 +260,8 @@ module RightSupport::DB
|
|
258
260
|
# block(Proc):: Code that will be called in keyspace context
|
259
261
|
def with_keyspace(keyspace, append_env=true, &block)
|
260
262
|
@@current_keyspace = keyspace
|
261
|
-
env
|
262
|
-
nspace
|
263
|
+
env = ENV['RACK_ENV'] || 'development'
|
264
|
+
nspace = namespace(env)
|
263
265
|
if append_env
|
264
266
|
if nspace
|
265
267
|
tail = "_#{env}_#{nspace}"
|
@@ -269,14 +271,14 @@ module RightSupport::DB
|
|
269
271
|
@@current_keyspace += tail unless @@current_keyspace.end_with?(tail)
|
270
272
|
end
|
271
273
|
block.call
|
272
|
-
|
273
|
-
|
274
|
+
ensure
|
275
|
+
@@current_keyspace = nil
|
274
276
|
end
|
275
277
|
|
276
278
|
def get_connection(current=nil)
|
277
|
-
config
|
279
|
+
config = env_config
|
278
280
|
thrift_client_options = {
|
279
|
-
:timeout
|
281
|
+
:timeout => RightSupport::DB::CassandraModel::DEFAULT_TIMEOUT,
|
280
282
|
:server_retry_period => nil,
|
281
283
|
}
|
282
284
|
|
@@ -289,14 +291,17 @@ module RightSupport::DB
|
|
289
291
|
current
|
290
292
|
end
|
291
293
|
|
292
|
-
#
|
293
|
-
# Create connection if does not already exist
|
294
|
-
# Use BinaryProtocolAccelerated if it available
|
294
|
+
# Return a Cassandra client object connected to a server and authorized to a suitable keyspace.
|
295
|
+
# Create connection if does not already exist; use BinaryProtocolAccelerated if available.
|
295
296
|
#
|
296
|
-
#
|
297
|
-
#
|
297
|
+
# This method determines the current keyspace based on the return value of self.keyspace
|
298
|
+
# which looks at the value of @@current_keyspace or @@default_keyspace to determine the keyspace it is operating
|
299
|
+
# under. If a connection already exists for the keyspace it will re-use it. If a connection does not exist,
|
300
|
+
# it will create a new persistent connection for that keyspace that can be re-used with future requests
|
301
|
+
#
|
302
|
+
# @return [Cassandra] a client object that can be used to send requests to the ring
|
298
303
|
def conn()
|
299
|
-
@@connections
|
304
|
+
@@connections ||= {}
|
300
305
|
@@connections[self.keyspace] = get_connection(@@connections[self.keyspace])
|
301
306
|
@@connections[self.keyspace]
|
302
307
|
end
|
@@ -351,9 +356,9 @@ module RightSupport::DB
|
|
351
356
|
elsif opt[:count]
|
352
357
|
do_op(:get, column_family, k, opt)
|
353
358
|
else
|
354
|
-
opt
|
359
|
+
opt = opt.clone
|
355
360
|
opt[:count] = DEFAULT_COUNT
|
356
|
-
columns
|
361
|
+
columns = Cassandra::OrderedHash.new
|
357
362
|
loop do
|
358
363
|
chunk = do_op(:get, column_family, k, opt)
|
359
364
|
columns.merge!(chunk)
|
@@ -375,7 +380,7 @@ module RightSupport::DB
|
|
375
380
|
# index(String):: Name of secondary index
|
376
381
|
# key(String):: Index value that each selected row is required to match
|
377
382
|
# columns(Array|nil):: Names of columns to be retrieved, defaults to all
|
378
|
-
# opt(Hash):: Request options with only :consistency used
|
383
|
+
# opt(Hash):: Request options with only :consistency and :count used
|
379
384
|
#
|
380
385
|
# === Block
|
381
386
|
# Optional block that is yielded each chunk as it is retrieved as an array
|
@@ -384,20 +389,20 @@ module RightSupport::DB
|
|
384
389
|
# === Return
|
385
390
|
# (OrderedHash):: Rows retrieved with each key, value is columns
|
386
391
|
def get_all_indexed_slices(index, key, columns = nil, opt = {})
|
387
|
-
rows
|
392
|
+
rows = Cassandra::OrderedHash.new
|
388
393
|
start = ""
|
389
394
|
count = opt.delete(:count) || DEFAULT_COUNT
|
390
|
-
expr
|
391
|
-
opt
|
395
|
+
expr = do_op(:create_idx_expr, index, key, "EQ")
|
396
|
+
opt = opt[:consistency] ? {:consistency => opt[:consistency]} : {}
|
392
397
|
while true
|
393
398
|
clause = do_op(:create_idx_clause, [expr], start, count)
|
394
|
-
chunk
|
399
|
+
chunk = self.conn.get_indexed_slices(column_family, clause, columns, opt)
|
395
400
|
rows.merge!(chunk)
|
396
401
|
if chunk.size == count
|
397
402
|
# Assume there are more chunks, use last key as start of next get
|
398
403
|
start = chunk.keys.last
|
399
404
|
else
|
400
|
-
|
405
|
+
# This must be the last chunk
|
401
406
|
break
|
402
407
|
end
|
403
408
|
end
|
@@ -407,88 +412,97 @@ module RightSupport::DB
|
|
407
412
|
# This method is an attempt to circumvent the Cassandra gem limitation of returning only 100 columns for wide rows,
|
408
413
|
# and also to help reliably iterate through a column family when the node is busy and experiencing many timeouts.
|
409
414
|
#
|
410
|
-
# Internally, it uses Cassandra#get_indexed_slices to find rows that match your index constraint;
|
411
|
-
#
|
415
|
+
# Internally, it uses Cassandra#get_indexed_slices to find rows that match your index constraint; for each matching
|
416
|
+
# row key, it iterates through all columns of that row, in chunks, using Cassandra#get_range. This approach is less
|
417
|
+
# efficient than grabbing some column values in the initial #get_indexed_slices, but it allows us to preserve the
|
418
|
+
# natural ordering of the columns we yield, and prevents us from yielding any column twice.
|
419
|
+
#
|
420
|
+
# A row key may be yielded more than once as each "chunk" of columns from that row is
|
421
|
+
# read from the ring, but each column will be yielded exactly once.
|
412
422
|
#
|
413
423
|
# == Parameters:
|
414
424
|
# @param [String] index column name
|
415
425
|
# @param [String] index column value
|
416
426
|
#
|
417
427
|
# == Yields:
|
418
|
-
# @yield [
|
428
|
+
# @yield [row_key, columns] yields one or more times for every row that contains a matching index column, ultimately yielding EVERY column in that row
|
429
|
+
# @yieldparam [String] row_key the row key currently being processes
|
430
|
+
# @yieldparam [Array] columns an array of Cassandra CassandraThrift::ColumnOrSuperColumn objects
|
419
431
|
def stream_all_indexed_slices(index, key)
|
420
|
-
expr
|
421
|
-
start_row
|
432
|
+
expr = do_op(:create_idx_expr, index, key, "EQ")
|
433
|
+
start_row = ''
|
422
434
|
|
423
435
|
# Loop over all CF rows, with batches of X
|
424
436
|
while (start_row != nil)
|
425
|
-
# Reset these to their
|
437
|
+
# Reset these to their initial values on every iteration thru the loop, in case
|
426
438
|
# we backed off due to timeouts (see rescue clauses below)
|
427
|
-
|
428
|
-
|
429
|
-
max_additional_column_count = 1000 # how much to grab in each chunk of a long row
|
439
|
+
row_count = 100 # how many rows to grab at once
|
440
|
+
column_count = 1000 # how much to grab in each chunk of a long row
|
430
441
|
|
431
|
-
clause = do_op(:create_idx_clause, [expr], start_row,
|
442
|
+
clause = do_op(:create_idx_clause, [expr], start_row, row_count)
|
432
443
|
|
433
|
-
#
|
434
|
-
#
|
444
|
+
# Ask for a single column from each row, because we don't care about the column values
|
445
|
+
# in this step; we just want the row keys that contain a matching index column.
|
435
446
|
begin
|
436
|
-
|
447
|
+
row_keys = self.conn.get_indexed_slices(column_family, clause, :count => 1).keys
|
437
448
|
rescue Exception => e
|
438
449
|
if retryable_read_timeout?(e)
|
439
|
-
logger.error "CassandraModel#stream_all_indexed_slices retrying get_indexed_slices with fewer rows
|
440
|
-
|
441
|
-
|
442
|
-
max_initial_column_count /= 10 if max_initial_column_count > 1
|
450
|
+
logger.error "CassandraModel#stream_all_indexed_slices retrying get_indexed_slices with fewer rows due to a %s: %s @ %s (cf='%s' start_row='%s' row_count=%d)" %
|
451
|
+
[e.class.name, e.message, e.backtrace.first, column_family, start_row, row_count]
|
452
|
+
row_count /= 10 if row_count > 1
|
443
453
|
retry
|
444
454
|
else
|
445
455
|
raise
|
446
456
|
end
|
447
457
|
end
|
448
458
|
|
449
|
-
|
450
|
-
# We already processed this row the previous iteration
|
459
|
+
row_keys.each do |row_key|
|
460
|
+
# We already processed this row the previous iteration; skip it
|
451
461
|
next if row_key == start_row
|
452
462
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
logger.error "CassandraModel#stream_all_indexed_slices retrying get_range with fewer rows/cols due to a %s: %s @ %s (cf='%s' row='%s' start='%s' count=%d)" %
|
472
|
-
[e.class.name, e.message, e.backtrace.first, column_family, row_key, last_column_name, max_additional_column_count]
|
473
|
-
max_additional_column_count /= 10 if max_additional_column_count > 1
|
474
|
-
retry
|
475
|
-
else
|
476
|
-
raise
|
477
|
-
end
|
463
|
+
start_column = ''
|
464
|
+
|
465
|
+
while start_column != nil
|
466
|
+
begin
|
467
|
+
options = {
|
468
|
+
:start_key => row_key,
|
469
|
+
:finish_key => row_key,
|
470
|
+
:start => start_column,
|
471
|
+
:count => column_count,
|
472
|
+
:slices_not_hash => true
|
473
|
+
}
|
474
|
+
|
475
|
+
columns = self.conn.get_range(column_family, options).first.columns
|
476
|
+
|
477
|
+
if columns[0].column.name == start_column
|
478
|
+
yield(row_key, columns[1..-1])
|
479
|
+
else
|
480
|
+
yield(row_key, columns)
|
478
481
|
end
|
479
482
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
+
if columns.size >= column_count
|
484
|
+
start_column = columns.last.column.name
|
485
|
+
else
|
486
|
+
start_column = nil
|
487
|
+
end
|
488
|
+
rescue Exception => e
|
489
|
+
if retryable_read_timeout?(e)
|
490
|
+
logger.error "CassandraModel#stream_all_indexed_slices retrying get_range with fewer cols due to a %s: %s @ %s (cf='%s' row='%s' start='%s' count=%d)" %
|
491
|
+
[e.class.name, e.message, e.backtrace.first, column_family, row_key, last_column_name, column_count]
|
492
|
+
column_count /= 10 if column_count > 1
|
493
|
+
retry
|
483
494
|
else
|
484
|
-
|
495
|
+
raise
|
485
496
|
end
|
486
497
|
end
|
487
498
|
end
|
488
499
|
end
|
489
500
|
|
490
|
-
|
491
|
-
|
501
|
+
if row_keys.size >= row_count
|
502
|
+
start_row = row_keys.last
|
503
|
+
else
|
504
|
+
start_row = nil
|
505
|
+
end
|
492
506
|
end
|
493
507
|
end
|
494
508
|
|
@@ -505,14 +519,14 @@ module RightSupport::DB
|
|
505
519
|
# given class as value, but object only contains values for the columns retrieved;
|
506
520
|
# array is always empty if a block is given
|
507
521
|
def get_indexed(index, key, columns = nil, opt = {})
|
508
|
-
rows
|
522
|
+
rows = []
|
509
523
|
start = ""
|
510
524
|
count = DEFAULT_COUNT
|
511
|
-
expr
|
512
|
-
opt
|
525
|
+
expr = do_op(:create_idx_expr, index, key, "EQ")
|
526
|
+
opt = opt[:consistency] ? {:consistency => opt[:consistency]} : {}
|
513
527
|
loop do
|
514
|
-
clause
|
515
|
-
chunk
|
528
|
+
clause = do_op(:create_idx_clause, [expr], start, count)
|
529
|
+
chunk = do_op(:get_indexed_slices, column_family, clause, columns, opt)
|
516
530
|
chunk_rows = []
|
517
531
|
chunk.each do |row_key, row_columns|
|
518
532
|
if row_columns && row_key != start
|
@@ -610,10 +624,10 @@ module RightSupport::DB
|
|
610
624
|
def do_op(meth, *args, &block)
|
611
625
|
first_started_at ||= Time.now
|
612
626
|
retries ||= 0
|
613
|
-
started_at
|
627
|
+
started_at = Time.now
|
614
628
|
|
615
629
|
# cassandra functionality
|
616
|
-
result
|
630
|
+
result = conn.send(meth, *args, &block)
|
617
631
|
|
618
632
|
# log functionality
|
619
633
|
do_op_log(first_started_at, started_at, retries, meth, args[0], args[1])
|
@@ -635,7 +649,7 @@ module RightSupport::DB
|
|
635
649
|
log_string = sprintf("CassandraModel %s, cf=%s, keys=%d, time=%.1fms", meth, cf, key_count, attempt_time*1000)
|
636
650
|
|
637
651
|
if retries && retries > 0
|
638
|
-
total_time
|
652
|
+
total_time = now - first_started_at
|
639
653
|
log_string += sprintf(", retries=%d, total_time=%.1fms", retries, total_time*1000)
|
640
654
|
end
|
641
655
|
|
@@ -724,7 +738,7 @@ module RightSupport::DB
|
|
724
738
|
# attrs(Hash):: Attributes for object which form Cassandra row
|
725
739
|
# with column name as key and column value as value
|
726
740
|
def initialize(key, attrs = {})
|
727
|
-
self.key
|
741
|
+
self.key = key
|
728
742
|
self.attributes = attrs
|
729
743
|
end
|
730
744
|
|