flying-sphinx 0.4.4 → 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 +4 -0
- data/Gemfile +3 -0
- data/LICENCE +1 -1
- data/Rakefile +27 -0
- data/VERSION +1 -1
- data/flying-sphinx.gemspec +38 -0
- data/lib/flying_sphinx.rb +0 -1
- data/lib/flying_sphinx/api.rb +65 -19
- data/lib/flying_sphinx/configuration.rb +61 -34
- data/lib/flying_sphinx/delayed_delta.rb +5 -1
- data/lib/flying_sphinx/index_request.rb +71 -28
- data/lib/flying_sphinx/rails.rb +5 -1
- data/lib/flying_sphinx/railtie.rb +5 -1
- data/lib/flying_sphinx/tasks.rb +12 -2
- data/lib/flying_sphinx/tunnel.rb +25 -16
- data/spec/spec_helper.rb +23 -0
- data/spec/specs/configuration_spec.rb +38 -0
- data/spec/{flying_sphinx → specs}/delayed_delta_spec.rb +0 -0
- data/spec/{flying_sphinx → specs}/flag_as_deleted_job_spec.rb +0 -0
- data/spec/specs/index_request_spec.rb +176 -0
- metadata +94 -121
- data/spec/flying_sphinx/configuration_spec.rb +0 -45
- data/spec/flying_sphinx/index_request_spec.rb +0 -122
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENCE
CHANGED
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
begin
|
3
|
+
require 'bundler'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
rescue LoadError
|
6
|
+
puts "Although not required, it's recommended you use bundler during development"
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
|
11
|
+
task :default => :test
|
12
|
+
task :test => :spec
|
13
|
+
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
|
16
|
+
RSpec::Core::RakeTask.new do |t|
|
17
|
+
t.pattern = 'spec/**/*_spec.rb'
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec::Core::RakeTask.new(:rspec) do |t|
|
21
|
+
t.pattern = 'spec/**/*_spec.rb'
|
22
|
+
t.rcov = true
|
23
|
+
t.rcov_opts = [
|
24
|
+
'--exclude', 'spec', '--exclude', 'gems',
|
25
|
+
'--exclude', 'riddle', '--exclude', 'ruby'
|
26
|
+
]
|
27
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = 'flying-sphinx'
|
4
|
+
s.version = '0.5.0'
|
5
|
+
s.authors = ['Pat Allan']
|
6
|
+
s.email = 'pat@freelancing-gods.com'
|
7
|
+
s.date = '2011-05-12'
|
8
|
+
s.summary = 'Sphinx in the Cloud'
|
9
|
+
s.description = 'Hooks Thinking Sphinx into the Flying Sphinx service'
|
10
|
+
s.homepage = 'https://flying-sphinx.com'
|
11
|
+
|
12
|
+
s.extra_rdoc_files = ['README.textile']
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.rubygems_version = %q{1.3.7}
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'thinking-sphinx', ['>= 0']
|
20
|
+
s.add_runtime_dependency 'net-ssh', ['~> 2.0.23']
|
21
|
+
s.add_runtime_dependency 'multi_json', ['~> 1.0.1']
|
22
|
+
s.add_runtime_dependency 'faraday', ['~> 0.6.1']
|
23
|
+
s.add_runtime_dependency 'faraday_middleware', ['~> 0.6.3']
|
24
|
+
s.add_runtime_dependency 'rash', ['~> 0.3.0']
|
25
|
+
|
26
|
+
s.add_development_dependency 'yajl-ruby', ['~> 0.8.2']
|
27
|
+
s.add_development_dependency 'rspec', ['~> 2.5.0']
|
28
|
+
s.add_development_dependency 'rcov', ['~> 0.9.9']
|
29
|
+
s.add_development_dependency 'fakeweb', ['~> 1.3.0']
|
30
|
+
s.add_development_dependency 'fakeweb-matcher', ['~> 1.2.2']
|
31
|
+
s.add_development_dependency 'delayed_job', ['~> 2.1.4']
|
32
|
+
|
33
|
+
s.post_install_message = <<-MESSAGE
|
34
|
+
If you're upgrading, you should rebuild your Sphinx setup when deploying:
|
35
|
+
|
36
|
+
$ heroku rake fs:rebuild
|
37
|
+
MESSAGE
|
38
|
+
end
|
data/lib/flying_sphinx.rb
CHANGED
data/lib/flying_sphinx/api.rb
CHANGED
@@ -1,33 +1,79 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
1
4
|
class FlyingSphinx::API
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
|
6
|
+
APIServer = 'https://flying-sphinx.com'
|
7
|
+
APIPath = "/api/my/app"
|
8
|
+
APIVersion = 2
|
9
|
+
|
10
|
+
attr_reader :api_key, :identifier, :adapter
|
11
|
+
|
12
|
+
def initialize(identifier, api_key, adapter = Faraday.default_adapter)
|
13
|
+
@api_key = api_key
|
10
14
|
@identifier = identifier
|
15
|
+
@adapter = adapter
|
11
16
|
end
|
12
|
-
|
17
|
+
|
13
18
|
def get(path, data = {})
|
14
|
-
|
19
|
+
log('GET', path, data) do
|
20
|
+
connection.get do |request|
|
21
|
+
request.url normalize_path(path), data
|
22
|
+
end
|
23
|
+
end
|
15
24
|
end
|
16
|
-
|
25
|
+
|
17
26
|
def post(path, data = {})
|
18
|
-
|
27
|
+
log('POST', path, data) do
|
28
|
+
connection.post normalize_path(path), data
|
29
|
+
end
|
19
30
|
end
|
20
|
-
|
31
|
+
|
21
32
|
def put(path, data = {})
|
22
|
-
|
33
|
+
log('PUT', path, data) do
|
34
|
+
connection.put normalize_path(path), data
|
35
|
+
end
|
23
36
|
end
|
24
|
-
|
37
|
+
|
25
38
|
private
|
26
|
-
|
27
|
-
def
|
39
|
+
|
40
|
+
def normalize_path(path)
|
41
|
+
path = (path == '/' ? nil : "/#{path}")
|
42
|
+
"#{APIPath}#{path}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def api_headers
|
28
46
|
{
|
29
|
-
|
30
|
-
|
47
|
+
'Accept' => "application/vnd.flying-sphinx-v#{APIVersion}+json",
|
48
|
+
'X-Flying-Sphinx-Token' => "#{identifier}:#{api_key}"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def connection(connection_options = {})
|
53
|
+
options = {
|
54
|
+
:ssl => {:verify => false},
|
55
|
+
:url => APIServer,
|
56
|
+
:headers => api_headers
|
31
57
|
}
|
58
|
+
|
59
|
+
Faraday.new(options) do |builder|
|
60
|
+
builder.use Faraday::Request::UrlEncoded
|
61
|
+
builder.use Faraday::Response::Rashify
|
62
|
+
builder.use Faraday::Response::ParseJson
|
63
|
+
builder.adapter(adapter)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def log(method, path, data = {}, option = {}, &block)
|
68
|
+
return block.call unless log?
|
69
|
+
|
70
|
+
puts "API Request: #{method} '#{path}'; params: #{data.inspect}"
|
71
|
+
response = block.call
|
72
|
+
puts "API Response: #{response.body.to_hash.inspect}"
|
73
|
+
return response
|
74
|
+
end
|
75
|
+
|
76
|
+
def log?
|
77
|
+
ENV['VERBOSE_LOGGING'].present?
|
32
78
|
end
|
33
79
|
end
|
@@ -1,104 +1,131 @@
|
|
1
1
|
class FlyingSphinx::Configuration
|
2
2
|
attr_reader :identifier, :api_key, :host, :port, :database_port, :mem_limit
|
3
|
-
|
3
|
+
|
4
4
|
def initialize(identifier = nil, api_key = nil)
|
5
5
|
@identifier = identifier || identifier_from_env
|
6
6
|
@api_key = api_key || api_key_from_env
|
7
|
-
|
7
|
+
|
8
8
|
set_from_server
|
9
9
|
setup_environment_settings
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def api
|
13
13
|
@api ||= FlyingSphinx::API.new(identifier, api_key)
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def sphinx_configuration
|
17
17
|
thinking_sphinx.generate
|
18
18
|
set_database_settings
|
19
|
-
|
19
|
+
|
20
20
|
riddle.render
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def start_sphinx
|
24
|
-
api.post('
|
24
|
+
api.post('start')
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def stop_sphinx
|
28
|
-
api.post('
|
28
|
+
api.post('stop')
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@host = json['server']
|
40
|
-
@port = json['port']
|
41
|
-
@database_port = json['database_port']
|
42
|
-
@mem_limit = json['mem_limit']
|
31
|
+
def client_key
|
32
|
+
"#{identifier}:#{api_key}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def output_recent_actions
|
36
|
+
api.get('actions').body.each do |action|
|
37
|
+
puts "#{action.created_at} #{action.name}"
|
38
|
+
end
|
43
39
|
end
|
44
40
|
|
41
|
+
private
|
42
|
+
|
43
|
+
def set_from_server
|
44
|
+
response = api.get '/'
|
45
|
+
raise 'Invalid Flying Sphinx credentials' if response.status == 403
|
46
|
+
|
47
|
+
@host = response.body.server
|
48
|
+
@port = response.body.port
|
49
|
+
@database_port = response.body.database_port
|
50
|
+
@mem_limit = response.body.mem_limit
|
51
|
+
rescue
|
52
|
+
# If the central Flying Sphinx server is down, let's use the environment
|
53
|
+
# variables so searching is still going to work.
|
54
|
+
@host = host_from_env
|
55
|
+
@port = port_from_env
|
56
|
+
end
|
57
|
+
|
45
58
|
def base_path
|
46
59
|
"/mnt/sphinx/flying-sphinx/#{identifier}"
|
47
60
|
end
|
48
|
-
|
61
|
+
|
49
62
|
def log_path
|
50
63
|
"#{base_path}/log"
|
51
64
|
end
|
52
|
-
|
65
|
+
|
53
66
|
def thinking_sphinx
|
54
67
|
ThinkingSphinx::Configuration.instance
|
55
68
|
end
|
56
|
-
|
69
|
+
|
57
70
|
def riddle
|
58
71
|
thinking_sphinx.configuration
|
59
72
|
end
|
60
|
-
|
73
|
+
|
61
74
|
def setup_environment_settings
|
62
75
|
ThinkingSphinx.remote_sphinx = true
|
63
|
-
|
76
|
+
|
64
77
|
set_searchd_settings
|
65
78
|
set_indexer_settings
|
66
79
|
set_path_settings
|
67
80
|
end
|
68
|
-
|
81
|
+
|
69
82
|
def set_path_settings
|
70
83
|
thinking_sphinx.searchd_file_path = "#{base_path}/indexes"
|
71
|
-
|
84
|
+
|
72
85
|
riddle.searchd.pid_file = "#{base_path}/searchd.pid"
|
73
86
|
riddle.searchd.log = "#{log_path}/searchd.log"
|
74
87
|
riddle.searchd.query_log = "#{log_path}/searchd.query.log"
|
75
88
|
end
|
76
|
-
|
89
|
+
|
77
90
|
def set_searchd_settings
|
78
91
|
thinking_sphinx.port = port
|
79
92
|
thinking_sphinx.address = host
|
93
|
+
|
94
|
+
if riddle.searchd.respond_to?(:client_key)
|
95
|
+
riddle.searchd.client_key = client_key
|
96
|
+
end
|
80
97
|
end
|
81
|
-
|
98
|
+
|
82
99
|
def set_indexer_settings
|
83
100
|
riddle.indexer.mem_limit = mem_limit.to_s + 'M'
|
84
101
|
end
|
85
|
-
|
102
|
+
|
86
103
|
def set_database_settings
|
104
|
+
return unless FlyingSphinx::Tunnel.required?
|
105
|
+
|
87
106
|
riddle.indexes.each do |index|
|
88
107
|
next unless index.respond_to?(:sources)
|
89
|
-
|
108
|
+
|
90
109
|
index.sources.each do |source|
|
91
110
|
source.sql_host = '127.0.0.1'
|
92
111
|
source.sql_port = database_port
|
93
112
|
end
|
94
113
|
end
|
95
114
|
end
|
96
|
-
|
115
|
+
|
97
116
|
def identifier_from_env
|
98
117
|
ENV['FLYING_SPHINX_IDENTIFIER']
|
99
118
|
end
|
100
|
-
|
119
|
+
|
101
120
|
def api_key_from_env
|
102
121
|
ENV['FLYING_SPHINX_API_KEY']
|
103
122
|
end
|
123
|
+
|
124
|
+
def host_from_env
|
125
|
+
ENV['FLYING_SPHINX_HOST']
|
126
|
+
end
|
127
|
+
|
128
|
+
def port_from_env
|
129
|
+
ENV['FLYING_SPHINX_PORT']
|
130
|
+
end
|
104
131
|
end
|
@@ -12,7 +12,11 @@ class FlyingSphinx::DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
|
|
12
12
|
def self.enqueue(object, priority = 0)
|
13
13
|
return if duplicates_exist? object
|
14
14
|
|
15
|
-
|
15
|
+
if defined?(Rails) && Rails.version.to_i <= 2
|
16
|
+
::Delayed::Job.enqueue(object, priority)
|
17
|
+
else
|
18
|
+
::Delayed::Job.enqueue(object, :priority => priority)
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
# Checks whether a given job already exists in the queue.
|
@@ -1,67 +1,107 @@
|
|
1
1
|
class FlyingSphinx::IndexRequest
|
2
2
|
attr_reader :index_id, :indices
|
3
|
-
|
3
|
+
|
4
|
+
INDEX_COMPLETE_CHECKING_INTERVAL = 3
|
5
|
+
|
4
6
|
# Remove all Delta jobs from the queue. If the
|
5
7
|
# delayed_jobs table does not exist, this method will do nothing.
|
6
|
-
#
|
8
|
+
#
|
7
9
|
def self.cancel_jobs
|
8
10
|
return unless defined?(::Delayed) && ::Delayed::Job.table_exists?
|
9
|
-
|
11
|
+
|
10
12
|
::Delayed::Job.delete_all "handler LIKE '--- !ruby/object:FlyingSphinx::%'"
|
11
13
|
end
|
12
|
-
|
14
|
+
|
15
|
+
def self.output_last_index
|
16
|
+
index = FlyingSphinx::Configuration.new.api.get('indices/last').body
|
17
|
+
puts "Index Job Status: #{index.status}"
|
18
|
+
puts "Index Log:\n#{index.log}"
|
19
|
+
end
|
20
|
+
|
13
21
|
def initialize(indices = [])
|
14
22
|
@indices = indices
|
15
23
|
end
|
16
|
-
|
24
|
+
|
17
25
|
# Shows index name in Delayed::Job#name.
|
18
|
-
#
|
26
|
+
#
|
19
27
|
def display_name
|
20
28
|
"#{self.class.name} for #{indices.join(', ')}"
|
21
29
|
end
|
22
|
-
|
30
|
+
|
23
31
|
def update_and_index
|
24
32
|
update_sphinx_configuration
|
25
33
|
index
|
26
34
|
end
|
27
35
|
|
36
|
+
def status_message
|
37
|
+
status = request_status
|
38
|
+
case status
|
39
|
+
when 'FINISHED'
|
40
|
+
'Index Request has completed.'
|
41
|
+
when 'FAILED'
|
42
|
+
'Index Request failed.'
|
43
|
+
when 'PENDING'
|
44
|
+
'Index Request is still pending - something has gone wrong.'
|
45
|
+
else
|
46
|
+
"Unknown index response: '#{status}'."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
28
50
|
# Runs Sphinx's indexer tool to process the index. Currently assumes Sphinx is
|
29
51
|
# running.
|
30
|
-
#
|
52
|
+
#
|
31
53
|
# @return [Boolean] true
|
32
|
-
#
|
54
|
+
#
|
33
55
|
def perform
|
34
56
|
index
|
35
57
|
true
|
36
58
|
end
|
37
|
-
|
59
|
+
|
38
60
|
private
|
39
|
-
|
61
|
+
|
40
62
|
def configuration
|
41
63
|
@configuration ||= FlyingSphinx::Configuration.new
|
42
64
|
end
|
43
|
-
|
65
|
+
|
44
66
|
def update_sphinx_configuration
|
45
|
-
api.put
|
67
|
+
api.put('/', :configuration => configuration.sphinx_configuration)
|
46
68
|
end
|
47
|
-
|
69
|
+
|
48
70
|
def index
|
49
|
-
FlyingSphinx::Tunnel.
|
50
|
-
|
51
|
-
|
52
|
-
|
71
|
+
if FlyingSphinx::Tunnel.required?
|
72
|
+
tunnelled_index
|
73
|
+
else
|
74
|
+
direct_index
|
53
75
|
end
|
54
76
|
rescue Net::SSH::Exception
|
55
|
-
|
77
|
+
# Server closed the connection on us. That's (hopefully) expected, nothing
|
78
|
+
# to worry about.
|
56
79
|
rescue RuntimeError => err
|
57
80
|
puts err.message
|
58
81
|
end
|
59
82
|
|
83
|
+
def tunnelled_index
|
84
|
+
FlyingSphinx::Tunnel.connect(configuration) do
|
85
|
+
begin_request unless request_begun?
|
86
|
+
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def direct_index
|
92
|
+
begin_request
|
93
|
+
while !request_complete?
|
94
|
+
sleep 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
60
98
|
def begin_request
|
61
|
-
|
99
|
+
response = api.post 'indices', :indices => indices.join(',')
|
100
|
+
|
101
|
+
@index_id = response.body.id
|
62
102
|
@request_begun = true
|
63
|
-
|
64
|
-
raise RuntimeError, 'Your account does not support delta indexing. Upgrading plans is probably the best way around this.' if
|
103
|
+
|
104
|
+
raise RuntimeError, 'Your account does not support delta indexing. Upgrading plans is probably the best way around this.' if response.body.status == 'BLOCKED'
|
65
105
|
end
|
66
106
|
|
67
107
|
def request_begun?
|
@@ -69,10 +109,8 @@ class FlyingSphinx::IndexRequest
|
|
69
109
|
end
|
70
110
|
|
71
111
|
def request_complete?
|
72
|
-
|
73
|
-
case response.body
|
112
|
+
case request_status
|
74
113
|
when 'FINISHED', 'FAILED'
|
75
|
-
puts "Indexing request failed." if response.body == 'FAILED'
|
76
114
|
true
|
77
115
|
when 'PENDING'
|
78
116
|
false
|
@@ -81,14 +119,19 @@ class FlyingSphinx::IndexRequest
|
|
81
119
|
end
|
82
120
|
end
|
83
121
|
|
122
|
+
def request_status
|
123
|
+
api.get("indices/#{index_id}").body.status
|
124
|
+
end
|
125
|
+
|
84
126
|
def cancel_request
|
85
127
|
return if index_id.nil?
|
86
|
-
|
128
|
+
|
87
129
|
puts "Connecting Flying Sphinx to the Database failed"
|
88
130
|
puts "Cancelling Index Request..."
|
89
|
-
|
131
|
+
|
132
|
+
api.put("indices/#{index_id}", :status => 'CANCELLED')
|
90
133
|
end
|
91
|
-
|
134
|
+
|
92
135
|
def api
|
93
136
|
configuration.api
|
94
137
|
end
|