solr_wrapper 0.0.7 → 1.0.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.
@@ -0,0 +1,148 @@
1
+ require 'delegate'
2
+ require 'faraday'
3
+
4
+ module SolrWrapper
5
+ # Configuraton that comes from static and dynamic sources.
6
+ class Settings < Delegator
7
+
8
+ def __getobj__
9
+ @static_config # return object we are delegating to, required
10
+ end
11
+
12
+ alias static_config __getobj__
13
+
14
+ def __setobj__(obj)
15
+ @static_config = obj
16
+ end
17
+
18
+ def initialize(static_config)
19
+ super
20
+ @static_config = static_config
21
+ end
22
+
23
+ ##
24
+ # Get the host this Solr instance is bound to
25
+ def host
26
+ '127.0.0.1'
27
+ end
28
+
29
+ def zookeeper_host
30
+ @zookeeper_host ||= static_config.zookeeper_port
31
+ @zookeeper_host ||= host
32
+ end
33
+
34
+ ##
35
+ # Get the port this Solr instance is running at
36
+ def port
37
+ @port ||= static_config.port
38
+ @port ||= random_open_port.to_s
39
+ end
40
+
41
+ def zookeeper_port
42
+ @zookeeper_port ||= static_config.zookeeper_port
43
+ @zookeeper_port ||= "#{port.to_i + 1000}"
44
+ end
45
+
46
+
47
+ ##
48
+ # Get a (likely) URL to the solr instance
49
+ def url
50
+ "http://#{host}:#{port}/solr/"
51
+ end
52
+
53
+ def instance_dir
54
+ @instance_dir ||= static_config.instance_dir
55
+ @instance_dir ||= File.join(tmpdir, File.basename(download_url, ".zip"))
56
+ end
57
+
58
+ def managed?
59
+ File.exist?(instance_dir)
60
+ end
61
+
62
+ def download_url
63
+ @download_url ||= static_config.url
64
+ @download_url ||= default_download_url
65
+ end
66
+
67
+ def solr_zip_path
68
+ @solr_zip_path ||= static_config.solr_zip_path
69
+ @solr_zip_path ||= default_solr_zip_path
70
+ end
71
+
72
+ def version_file
73
+ static_config.version_file || File.join(instance_dir, "VERSION")
74
+ end
75
+
76
+ def md5url
77
+ if default_download_url == archive_download_url
78
+ "#{archive_download_url}.md5"
79
+ else
80
+ "http://www.us.apache.org/dist/lucene/solr/#{static_config.version}/solr-#{static_config.version}.zip.md5"
81
+ end
82
+ end
83
+
84
+ def md5sum_path
85
+ File.join(download_dir, File.basename(md5url))
86
+ end
87
+
88
+ def solr_binary
89
+ File.join(instance_dir, "bin", "solr")
90
+ end
91
+
92
+ def tmp_save_dir
93
+ @tmp_save_dir ||= Dir.mktmpdir
94
+ end
95
+
96
+ private
97
+
98
+ def tmpdir
99
+ if defined?(Rails) && Rails.root
100
+ File.join(Rails.root, 'tmp')
101
+ else
102
+ Dir.tmpdir
103
+ end
104
+ end
105
+
106
+ def default_solr_zip_path
107
+ File.join(download_dir, File.basename(download_url))
108
+ end
109
+
110
+ def download_dir
111
+ @download_dir ||= static_config.download_dir
112
+ FileUtils.mkdir_p @download_dir
113
+ @download_dir
114
+ end
115
+
116
+ def default_download_url
117
+ @default_url ||= begin
118
+ json = open(static_config.mirror_url).read
119
+ doc = JSON.parse(json)
120
+ url = doc['preferred'] + doc['path_info']
121
+
122
+ response = Faraday.head(url)
123
+
124
+ if response.success?
125
+ url
126
+ else
127
+ archive_download_url
128
+ end
129
+ end
130
+ rescue Errno::ECONNRESET, SocketError, Faraday::Error
131
+ archive_download_url
132
+ end
133
+
134
+ def archive_download_url
135
+ "https://archive.apache.org/dist/lucene/solr/#{static_config.version}/solr-#{static_config.version}.zip"
136
+ end
137
+
138
+ def random_open_port
139
+ socket = Socket.new(:INET, :STREAM, 0)
140
+ begin
141
+ socket.bind(Addrinfo.tcp('127.0.0.1', 0))
142
+ socket.local_address.ip_port
143
+ ensure
144
+ socket.close
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,45 @@
1
+ require 'solr_wrapper'
2
+
3
+ ## These tasks get loaded into the host context when solr_wrapper is required
4
+ namespace :solr do
5
+ desc "Load the solr options and solr instance"
6
+ task :environment do
7
+ @solr_instance = SolrWrapper.default_instance
8
+ end
9
+
10
+ desc 'Install a clean version of solr. Replaces the existing copy if there is one.'
11
+ task clean: :environment do
12
+ puts "Installing clean version of solr at #{File.expand_path(@solr_instance.instance_dir)}"
13
+ @solr_instance.remove_instance_dir!
14
+ @solr_instance.extract_and_configure
15
+ end
16
+
17
+ desc 'start solr'
18
+ task start: :environment do
19
+ begin
20
+ puts "Starting solr at #{@solr_instance.config.url}"
21
+ @solr_instance.start
22
+ rescue => e
23
+ if e.message.include?("Port #{@solr_instance.port} is already being used by another process")
24
+ puts "FAILED. Port #{@solr_instance.port} is already being used."
25
+ puts " Did you already have solr running?"
26
+ puts " a) YES: Continue as you were. Solr is running."
27
+ puts " b) NO: Either set SOLR_OPTIONS[:port] to a different value or stop the process that's using port #{@solr_instance.port}."
28
+ else
29
+ raise "Failed to start solr. #{e.class}: #{e.message}"
30
+ end
31
+ end
32
+ end
33
+
34
+ desc 'restart solr'
35
+ task restart: :environment do
36
+ puts "Restarting solr"
37
+ @solr_instance.restart
38
+ end
39
+
40
+ desc 'stop solr'
41
+ task stop: :environment do
42
+ @solr_instance.stop
43
+ end
44
+
45
+ end
@@ -1,3 +1,3 @@
1
1
  module SolrWrapper
2
- VERSION = "0.0.7"
3
- end
2
+ VERSION = '1.0.0'
3
+ end
data/lib/solr_wrapper.rb CHANGED
@@ -1,18 +1,44 @@
1
1
  require 'solr_wrapper/version'
2
+ require 'solr_wrapper/configuration'
3
+ require 'solr_wrapper/settings'
4
+ require 'solr_wrapper/md5'
5
+ require 'solr_wrapper/downloader'
2
6
  require 'solr_wrapper/instance'
7
+ require 'solr_wrapper/client'
3
8
 
4
9
  module SolrWrapper
5
10
  def self.default_solr_version
6
- "5.0.0"
11
+ 'latest'
7
12
  end
8
13
 
9
- def self.default_instance options = {}
10
- @default_instance ||= SolrWrapper::Instance.new options
14
+ def self.default_solr_port
15
+ '8983'
16
+ end
17
+
18
+ def self.default_instance_options
19
+ @default_instance_options ||= {
20
+ port: SolrWrapper.default_solr_port,
21
+ version: SolrWrapper.default_solr_version
22
+ }
23
+ end
24
+
25
+ def self.default_instance_options=(options)
26
+ @default_instance_options = options
27
+ end
28
+
29
+ def self.default_instance(options = {})
30
+ @default_instance ||= instance(default_instance_options)
31
+ end
32
+
33
+ def self.instance(options = {})
34
+ SolrWrapper::Instance.new(options)
11
35
  end
12
36
 
13
37
  ##
14
38
  # Ensures a Solr service is running before executing the block
15
- def self.wrap options = {}, &block
16
- default_instance(options).wrap &block
39
+ def self.wrap(options = {}, &block)
40
+ instance(options).wrap &block
17
41
  end
18
- end
42
+
43
+ class SolrWrapperError < StandardError; end
44
+ end
data/solr_wrapper.gemspec CHANGED
@@ -13,10 +13,12 @@ Gem::Specification.new do |spec|
13
13
  spec.license = "MIT"
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
19
  spec.require_paths = ["lib"]
19
20
 
21
+ spec.add_dependency "faraday"
20
22
  spec.add_dependency "rubyzip"
21
23
  spec.add_dependency "ruby-progressbar"
22
24
 
@@ -26,4 +28,5 @@ Gem::Specification.new do |spec|
26
28
  spec.add_development_dependency "rspec"
27
29
  spec.add_development_dependency "simple_solr_client"
28
30
  spec.add_development_dependency "coveralls"
31
+ spec.add_development_dependency "webmock"
29
32
  end
@@ -0,0 +1,2 @@
1
+ port: 9998
2
+ verbose: true
@@ -0,0 +1,8 @@
1
+ port: 9999
2
+ collection:
3
+ dir: solr/config/
4
+ name: project-development
5
+ persist: false
6
+ configsets:
7
+ - dir: solr/config/
8
+ name: project-development-configset
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe SolrWrapper::Client do
4
+ subject { described_class.new('http://localhost:8983/solr/') }
5
+
6
+ describe '#exists?' do
7
+ it 'checks if a solrcloud collection exists' do
8
+ stub_request(:get, 'http://localhost:8983/solr/admin/collections?action=LIST&wt=json').to_return(body: '{ "collections": ["x", "y", "z"]}')
9
+ stub_request(:get, 'http://localhost:8983/solr/admin/cores?action=STATUS&wt=json&core=a').to_return(body: '{ "status": { "a": {} } }')
10
+
11
+ expect(subject.exists?('x')).to eq true
12
+ expect(subject.exists?('a')).to eq false
13
+ end
14
+
15
+ it 'checks if a solr core exists' do
16
+ stub_request(:get, 'http://localhost:8983/solr/admin/collections?action=LIST&wt=json').to_return(body: '{ "error": { "msg": "Solr instance is not running in SolrCloud mode."} }')
17
+
18
+ stub_request(:get, 'http://localhost:8983/solr/admin/cores?action=STATUS&wt=json&core=x').to_return(body: '{ "status": { "x": { "name": "x" } } }')
19
+ stub_request(:get, 'http://localhost:8983/solr/admin/cores?action=STATUS&wt=json&core=a').to_return(body: '{ "status": { "a": {} } }')
20
+
21
+ expect(subject.exists?('x')).to eq true
22
+ expect(subject.exists?('a')).to eq false
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe SolrWrapper::Configuration do
4
+ let(:config) { described_class.new options }
5
+
6
+ describe "#port" do
7
+ subject { config.port }
8
+
9
+ context "when port is set to nil" do
10
+ let(:options) { { port: nil } }
11
+ it { is_expected.to eq nil }
12
+ end
13
+
14
+ context "when port is not set" do
15
+ let(:options) { {} }
16
+ it { is_expected.to eq '8983' }
17
+ end
18
+
19
+ context "when a port is provided" do
20
+ let(:options) { { port: '8888' } }
21
+ it { is_expected.to eq '8888' }
22
+ end
23
+ end
24
+
25
+ describe "#load_configs" do
26
+ before do
27
+ allow(config).to receive(:default_configuration_paths).and_return([])
28
+ end
29
+ context 'with a single config file' do
30
+ let(:options) { { config: 'spec/fixtures/sample_config.yml' } }
31
+ it "uses values from the config file" do
32
+ expect(config.port).to eq '9999'
33
+ end
34
+ end
35
+ context 'with multiple config files' do
36
+ let(:options) { { config: ['spec/fixtures/sample_config.yml', 'spec/fixtures/another_sample_config.yml'] } }
37
+ it "uses values from the config file" do
38
+ expect(config.port).to eq '9998'
39
+ expect(config.verbose?).to eq true
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#collection_options" do
45
+ before do
46
+ allow(config).to receive(:default_configuration_paths).and_return([])
47
+ end
48
+ let(:options) { { config: 'spec/fixtures/sample_config.yml' } }
49
+ it "uses values from the config file" do
50
+ expect(config.collection_options).to eq(name: 'project-development', dir: 'solr/config/', persist: false)
51
+ end
52
+ end
53
+
54
+ describe '#configsets' do
55
+ before do
56
+ allow(config).to receive(:default_configuration_paths).and_return([])
57
+ end
58
+ let(:options) { { config: 'spec/fixtures/sample_config.yml' } }
59
+
60
+ it 'uses values from the config file' do
61
+ expect(config.configsets).to include(name: 'project-development-configset', dir: 'solr/config/')
62
+ end
63
+ end
64
+
65
+ describe '#version' do
66
+ context 'when it is a version number' do
67
+ let(:options) { { version: 'x.y.z' } }
68
+
69
+ it 'uses that version' do
70
+ expect(config.version).to eq 'x.y.z'
71
+ end
72
+ end
73
+
74
+ context 'when it is "latest"' do
75
+ let(:options) { { version: 'latest'} }
76
+
77
+ before do
78
+ stub_request(:get, 'https://svn.apache.org/repos/asf/lucene/cms/trunk/content/latestversion.mdtext').to_return(body: 'z.y.x')
79
+ end
80
+
81
+ it 'fetches the latest version number from apache' do
82
+ expect(config.version).to eq 'z.y.x'
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,19 +1,166 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe SolrWrapper::Instance do
4
- subject { SolrWrapper::Instance.new }
5
- let(:client) { SimpleSolrClient::Client.new("http://localhost:#{subject.port}/solr/") }
4
+ let(:options) { {} }
5
+ let(:solr_instance) { SolrWrapper::Instance.new(options) }
6
+ subject { solr_instance }
7
+ let(:client) { SimpleSolrClient::Client.new(subject.url) }
8
+
6
9
  describe "#with_collection" do
7
- it "should create a new anonymous collection" do
10
+ let(:options) { { cloud: false } }
11
+ context "without a name" do
12
+ it "creates a new anonymous collection" do
13
+ subject.wrap do |solr|
14
+ solr.with_collection(dir: File.join(FIXTURES_DIR, "basic_configs")) do |collection_name|
15
+ core = client.core(collection_name)
16
+ unless defined? JRUBY_VERSION
17
+ expect(core.schema.field('id').name).to eq 'id'
18
+ expect(core.schema.field('id').stored).to eq true
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ context "with a config file" do
25
+ before do
26
+ allow(solr_instance.config).to receive(:collection_options)
27
+ .and_return(name: 'project-development', dir: 'solr/config/')
28
+ allow(solr_instance).to receive(:delete)
29
+ end
30
+
31
+ it "creates a new collection with options from the config" do
32
+ expect(solr_instance).to receive(:create).with(
33
+ hash_including(name: "project-development", dir: anything))
34
+ solr_instance.with_collection(dir: File.join(FIXTURES_DIR, "basic_configs")) {}
35
+ end
36
+ end
37
+
38
+ context 'persistent collections' do
39
+ it "creates a new collection with options from the config" do
40
+ expect(solr_instance).to receive(:create).with(
41
+ hash_including(name: 'project-development'))
42
+ expect(solr_instance).not_to receive(:delete)
43
+ solr_instance.with_collection(name: 'project-development', dir: 'solr/config/', persist: true) {}
44
+ end
45
+
46
+ describe 'single solr node' do
47
+ it 'allows persistent collection on restart' do
48
+ subject.wrap do |solr|
49
+ solr.with_collection(name: 'solr-node-persistent-core', dir: File.join(FIXTURES_DIR, 'basic_configs'), persist: true) {}
50
+ end
51
+
52
+ subject.wrap do |solr|
53
+ solr.with_collection(name: 'solr-node-persistent-core', dir: File.join(FIXTURES_DIR, 'basic_configs'), persist: true) {}
54
+ solr.delete 'solr-node-persistent-core'
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'solr cloud' do
60
+ let(:options) { { cloud: true } }
61
+
62
+ it 'allows persistent collection on restart' do
63
+ subject.wrap do |solr|
64
+ config_name = solr.upconfig dir: File.join(FIXTURES_DIR, 'basic_configs')
65
+ solr.with_collection(name: 'solr-cloud-persistent-collection', config_name: config_name, persist: true) {}
66
+ end
67
+
68
+ subject.wrap do |solr|
69
+ solr.with_collection(name: 'solr-cloud-persistent-collection', persist: true) {}
70
+ solr.delete 'solr-cloud-persistent-collection'
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'with a SolrCloud instance' do
78
+ let(:options) { { cloud: true } }
79
+ it 'can upload configurations' do
8
80
  subject.wrap do |solr|
9
- solr.with_collection(dir: File.join(FIXTURES_DIR, "basic_configs")) do |collection_name|
81
+ config_name = solr.upconfig dir: File.join(FIXTURES_DIR, 'basic_configs')
82
+ Dir.mktmpdir do |dir|
83
+ solr.downconfig name: config_name, dir: dir
84
+ end
85
+ solr.with_collection(config_name: config_name) do |collection_name|
10
86
  core = client.core(collection_name)
11
87
  unless defined? JRUBY_VERSION
12
- expect(core.schema.field('id').name).to eq 'id'
13
- expect(core.schema.field('id').stored).to eq true
88
+ expect(core.all.size).to eq 0
14
89
  end
15
90
  end
16
91
  end
17
92
  end
93
+
94
+ context 'with a config file' do
95
+ before do
96
+ allow(solr_instance.config).to receive(:configsets)
97
+ .and_return([name: 'project-development', dir: 'solr/config/'])
98
+ end
99
+
100
+ it 'creates a new configsets with options from the config' do
101
+ expect(subject).to receive(:upconfig).with(
102
+ hash_including(name: 'project-development', dir: anything))
103
+
104
+ subject.wrap do
105
+ # no-op
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ describe 'exec' do
112
+ let(:cmd) { 'start' }
113
+ let(:options) { { p: '4098', help: true } }
114
+ subject { solr_instance.send(:exec, cmd, options) }
115
+ it 'runs the command' do
116
+ result_io = subject
117
+ expect(result_io.read).to include('Usage: solr start')
118
+ end
119
+ it 'accepts boolean flags' do
120
+ result_io = solr_instance.send(:exec, 'start', p: '4098', help: true)
121
+ expect(result_io.read).to include('Usage: solr start')
122
+ end
123
+
124
+ describe 'when something goes wrong' do
125
+ let(:cmd) { 'healthcheck' }
126
+ let(:options) { { z: 'localhost:5098' } }
127
+ it 'raises an error with the output from the shell command' do
128
+ expect { subject }.to raise_error(RuntimeError, /Failed to execute solr healthcheck: collection parameter is required!/)
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "#host" do
134
+ subject { solr_instance.host }
135
+ it { is_expected.to eq '127.0.0.1' }
136
+ end
137
+
138
+ describe "#port" do
139
+ subject { solr_instance.port }
140
+ it { is_expected.to eq '8983' }
141
+ end
142
+
143
+ describe "#url" do
144
+ subject { solr_instance.url }
145
+ it { is_expected.to eq 'http://127.0.0.1:8983/solr/' }
146
+ end
147
+
148
+ describe "#instance_dir" do
149
+ subject { solr_instance.instance_dir }
150
+ it { is_expected.to start_with Dir.tmpdir }
151
+ end
152
+
153
+ describe "#version" do
154
+ before do
155
+ allow(solr_instance.config).to receive(:version).and_return('solr-version-number')
156
+ end
157
+
158
+ subject { solr_instance.version }
159
+ it { is_expected.to eq 'solr-version-number' }
160
+ end
161
+
162
+ describe "#md5" do
163
+ subject { solr_instance.md5 }
164
+ it { is_expected.to be_instance_of SolrWrapper::MD5 }
18
165
  end
19
- end
166
+ end
@@ -4,12 +4,27 @@ describe SolrWrapper do
4
4
  describe ".wrap" do
5
5
  it "should launch solr" do
6
6
  SolrWrapper.wrap do |solr|
7
- expect {
7
+ expect do
8
8
  Timeout::timeout(15) do
9
9
  TCPSocket.new('127.0.0.1', solr.port).close
10
10
  end
11
- }.not_to raise_exception
11
+ end.not_to raise_exception
12
12
  end
13
13
  end
14
14
  end
15
- end
15
+
16
+ describe '.instance' do
17
+ context 'without arguments' do
18
+ it 'inherits the defaults' do
19
+ expect(SolrWrapper.instance.port).to eq '8983'
20
+ end
21
+ end
22
+ end
23
+
24
+ describe ".default_instance_options=" do
25
+ it "sets default options" do
26
+ SolrWrapper.default_instance_options = { port: '1234' }
27
+ expect(SolrWrapper.default_instance_options[:port]). to eq '1234'
28
+ end
29
+ end
30
+ end
data/spec/spec_helper.rb CHANGED
@@ -5,9 +5,11 @@ require 'solr_wrapper'
5
5
  require 'simple_solr_client'
6
6
 
7
7
  require 'rspec'
8
+ require 'webmock/rspec'
8
9
 
9
- FIXTURES_DIR = File.expand_path("fixtures", File.dirname(__FILE__))
10
+ WebMock.allow_net_connect!
10
11
 
11
- RSpec.configure do |config|
12
+ FIXTURES_DIR = File.expand_path("fixtures", File.dirname(__FILE__))
12
13
 
13
- end
14
+ RSpec.configure do |_config|
15
+ end