elasticsearch-extensions 0.0.3 → 0.0.33
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.
- checksums.yaml +7 -0
- data/Gemfile +29 -0
- data/LICENSE.txt +199 -10
- data/README.md +233 -25
- data/Rakefile +33 -31
- data/elasticsearch-extensions.gemspec +46 -24
- data/lib/elasticsearch/extensions/ansi/actions.rb +45 -1
- data/lib/elasticsearch/extensions/ansi/helpers.rb +17 -0
- data/lib/elasticsearch/extensions/ansi/response.rb +23 -2
- data/lib/elasticsearch/extensions/ansi.rb +17 -0
- data/lib/elasticsearch/extensions/backup.rb +202 -0
- data/lib/elasticsearch/extensions/reindex.rb +187 -0
- data/lib/elasticsearch/extensions/test/cluster/tasks.rb +30 -0
- data/lib/elasticsearch/extensions/test/cluster.rb +701 -0
- data/lib/elasticsearch/extensions/test/profiling.rb +124 -0
- data/lib/elasticsearch/extensions/test/startup_shutdown.rb +71 -0
- data/lib/elasticsearch/extensions/version.rb +18 -1
- data/lib/elasticsearch/extensions.rb +19 -0
- data/lib/elasticsearch-extensions.rb +5 -0
- data/test/ansi/unit/ansi_test.rb +66 -0
- data/test/backup/unit/backup_test.rb +131 -0
- data/test/reindex/integration/reindex_test.rb +107 -0
- data/test/reindex/unit/reindex_test.rb +123 -0
- data/test/test/cluster/integration/cluster_test.rb +66 -0
- data/test/test/cluster/unit/cluster_test.rb +363 -0
- data/test/test_helper.rb +78 -0
- metadata +109 -89
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'ruby-prof'
|
|
19
|
+
require 'benchmark'
|
|
20
|
+
require 'ansi'
|
|
21
|
+
|
|
22
|
+
module Elasticsearch
|
|
23
|
+
module Extensions
|
|
24
|
+
module Test
|
|
25
|
+
|
|
26
|
+
# Allows to define and execute profiling tests within [Shoulda](https://github.com/thoughtbot/shoulda) contexts.
|
|
27
|
+
#
|
|
28
|
+
# Measures operations and reports statistics, including code profile.
|
|
29
|
+
#
|
|
30
|
+
# Uses the "benchmark" standard library and the "ruby-prof" gem.
|
|
31
|
+
#
|
|
32
|
+
# File: profiling_test.rb
|
|
33
|
+
#
|
|
34
|
+
# require 'test/unit'
|
|
35
|
+
# require 'shoulda/context'
|
|
36
|
+
# require 'elasticsearch/extensions/test/profiling'
|
|
37
|
+
#
|
|
38
|
+
# class ProfilingTest < Test::Unit::TestCase
|
|
39
|
+
# extend Elasticsearch::Extensions::Test::Profiling
|
|
40
|
+
#
|
|
41
|
+
# context "Mathematics" do
|
|
42
|
+
# measure "divide numbers", count: 10_000 do
|
|
43
|
+
# assert_nothing_raised { 1/2 }
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# $ QUIET=y ruby profiling_test.rb
|
|
50
|
+
#
|
|
51
|
+
# ...
|
|
52
|
+
# ProfilingTest
|
|
53
|
+
#
|
|
54
|
+
# -------------------------------------------------------------------------------
|
|
55
|
+
# Context: Mathematics should divide numbers (10000x)
|
|
56
|
+
# mean: 0.03ms | avg: 0.03ms | max: 0.14ms
|
|
57
|
+
# -------------------------------------------------------------------------------
|
|
58
|
+
# PASS (0:00:00.490) test: Mathematics should divide numbers (10000x).
|
|
59
|
+
# ...
|
|
60
|
+
#
|
|
61
|
+
module Profiling
|
|
62
|
+
|
|
63
|
+
# Profiles the passed block of code.
|
|
64
|
+
#
|
|
65
|
+
# measure "divide numbers", count: 10_000 do
|
|
66
|
+
# assert_nothing_raised { 1/2 }
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# @todo Try to make progress bar not to interfere with tests
|
|
70
|
+
#
|
|
71
|
+
def measure(name, options={}, &block)
|
|
72
|
+
___ = '-'*ANSI::Terminal.terminal_width
|
|
73
|
+
test_name = name
|
|
74
|
+
suite_name = self.name.split('::').last
|
|
75
|
+
context_name = self.context(nil) {}.first.parent.name
|
|
76
|
+
count = Integer(ENV['COUNT'] || options[:count] || 1_000)
|
|
77
|
+
ticks = []
|
|
78
|
+
progress = ANSI::Progressbar.new(suite_name, count) unless ENV['QUIET'] || options[:quiet]
|
|
79
|
+
|
|
80
|
+
should "#{test_name} (#{count}x)" do
|
|
81
|
+
RubyProf.start
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
count.times do
|
|
85
|
+
ticks << Benchmark.realtime { self.instance_eval(&block) }
|
|
86
|
+
|
|
87
|
+
if progress
|
|
88
|
+
RubyProf.pause
|
|
89
|
+
progress.inc
|
|
90
|
+
RubyProf.resume
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
ensure
|
|
94
|
+
result = RubyProf.stop
|
|
95
|
+
progress.finish if progress
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
total = result.threads.reduce(0) { |t,info| t += info.total_time; t }
|
|
99
|
+
mean = (ticks.sort[(ticks.size/2).round-1])*1000
|
|
100
|
+
avg = (ticks.inject {|sum,el| sum += el; sum}.to_f/ticks.size)*1000
|
|
101
|
+
min = ticks.min*1000
|
|
102
|
+
max = ticks.max*1000
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
result.eliminate_methods!([/Integer#times|Benchmark.realtime|ANSI::Code#.*|ANSI::ProgressBar#.*/])
|
|
106
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
107
|
+
# printer = RubyProf::GraphPrinter.new(result)
|
|
108
|
+
|
|
109
|
+
puts "\n",
|
|
110
|
+
___,
|
|
111
|
+
"#{suite_name}: " + ANSI.bold(context_name) + ' should ' + ANSI.bold(name) + " (#{count}x)",
|
|
112
|
+
"total: #{sprintf('%.2f', total)}s | " +
|
|
113
|
+
"mean: #{sprintf('%.2f', mean)}ms | " +
|
|
114
|
+
"avg: #{sprintf('%.2f', avg)}ms | " +
|
|
115
|
+
"min: #{sprintf('%.2f', min)}ms | " +
|
|
116
|
+
"max: #{sprintf('%.2f', max)}ms",
|
|
117
|
+
___
|
|
118
|
+
printer.print(STDOUT, {}) unless ENV['QUIET'] || options[:quiet]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
module Elasticsearch
|
|
19
|
+
module Extensions
|
|
20
|
+
module Test
|
|
21
|
+
# Startup/shutdown support for test suites
|
|
22
|
+
#
|
|
23
|
+
# Example:
|
|
24
|
+
#
|
|
25
|
+
# class MyTest < Test::Unit::TestCase
|
|
26
|
+
# extend Elasticsearch::Extensions::Test::StartupShutdown
|
|
27
|
+
#
|
|
28
|
+
# startup { puts "Suite starting up..." }
|
|
29
|
+
# shutdown { puts "Suite shutting down..." }
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# *** IMPORTANT NOTE: **********************************************************
|
|
33
|
+
#
|
|
34
|
+
# You have to register the handler for shutdown before requiring 'test/unit':
|
|
35
|
+
#
|
|
36
|
+
# # File: test_helper.rb
|
|
37
|
+
# at_exit { MyTest.__run_at_exit_hooks }
|
|
38
|
+
# require 'test/unit'
|
|
39
|
+
#
|
|
40
|
+
# The API follows Test::Unit 2.0
|
|
41
|
+
# <https://github.com/test-unit/test-unit/blob/master/lib/test/unit/testcase.rb>
|
|
42
|
+
#
|
|
43
|
+
module StartupShutdown
|
|
44
|
+
@@started = false
|
|
45
|
+
@@shutdown_blocks ||= []
|
|
46
|
+
|
|
47
|
+
def startup &block
|
|
48
|
+
return if started?
|
|
49
|
+
@@started = true
|
|
50
|
+
yield block if block_given?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def shutdown &block
|
|
54
|
+
@@shutdown_blocks << block if block_given?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def started?
|
|
58
|
+
!! @@started
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def __run_at_exit_hooks
|
|
62
|
+
return unless started?
|
|
63
|
+
STDERR.puts ANSI.faint("Running at_exit hooks...")
|
|
64
|
+
puts ANSI.faint('-'*80)
|
|
65
|
+
@@shutdown_blocks.each { |b| b.call }
|
|
66
|
+
puts ANSI.faint('-'*80)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
1
18
|
module Elasticsearch
|
|
2
19
|
module Extensions
|
|
3
|
-
VERSION =
|
|
20
|
+
VERSION = '0.0.33'.freeze
|
|
4
21
|
end
|
|
5
22
|
end
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
# encoding: utf-8
|
|
19
|
+
|
|
1
20
|
require 'elasticsearch'
|
|
2
21
|
require 'elasticsearch/extensions/version'
|
|
3
22
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'test_helper'
|
|
19
|
+
require 'elasticsearch/extensions/ansi'
|
|
20
|
+
|
|
21
|
+
class Elasticsearch::Extensions::AnsiTest < Elasticsearch::Test::UnitTestCase
|
|
22
|
+
context "The ANSI extension" do
|
|
23
|
+
setup do
|
|
24
|
+
@client = Elasticsearch::Client.new
|
|
25
|
+
@client.stubs(:perform_request).returns \
|
|
26
|
+
Elasticsearch::Transport::Transport::Response.new(200, { "ok" => true, "status" => 200, "name" => "Hit-Maker",
|
|
27
|
+
"version" => { "number" => "0.90.7",
|
|
28
|
+
"build_hash" => "abc123",
|
|
29
|
+
"build_timestamp"=>"2013-11-13T12:06:54Z", "build_snapshot"=>false, "lucene_version"=>"4.5.1" },
|
|
30
|
+
"tagline"=>"You Know, for Search" })
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
should "wrap the response" do
|
|
34
|
+
response = @client.info
|
|
35
|
+
|
|
36
|
+
assert_instance_of Elasticsearch::Extensions::ANSI::ResponseBody, response
|
|
37
|
+
assert_instance_of Hash, response.to_hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
should "extend the response object with `to_ansi`" do
|
|
41
|
+
response = @client.info
|
|
42
|
+
|
|
43
|
+
assert_respond_to response, :to_ansi
|
|
44
|
+
assert_instance_of String, response.to_ansi
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
should "call the 'awesome_inspect' method when available and no handler found" do
|
|
48
|
+
@client.stubs(:perform_request).returns \
|
|
49
|
+
Elasticsearch::Transport::Transport::Response.new(200, {"index-1"=>{"aliases"=>{}}})
|
|
50
|
+
response = @client.cat.aliases
|
|
51
|
+
|
|
52
|
+
response.instance_eval do
|
|
53
|
+
def awesome_inspect; "---PRETTY---"; end
|
|
54
|
+
end
|
|
55
|
+
assert_equal '---PRETTY---', response.to_ansi
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
should "call `to_s` method when no pretty printer or handler found" do
|
|
59
|
+
@client.stubs(:perform_request).returns \
|
|
60
|
+
Elasticsearch::Transport::Transport::Response.new(200, {"index-1"=>{"aliases"=>{}}})
|
|
61
|
+
response = @client.cat.aliases
|
|
62
|
+
|
|
63
|
+
assert_equal '{"index-1"=>{"aliases"=>{}}}', response.to_ansi
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'test_helper'
|
|
19
|
+
require 'logger'
|
|
20
|
+
|
|
21
|
+
# Mock the Backup modules and classes so we're not depending on the gem in the unit test
|
|
22
|
+
#
|
|
23
|
+
module Backup
|
|
24
|
+
class Error < StandardError; end
|
|
25
|
+
|
|
26
|
+
class Logger < ::Logger
|
|
27
|
+
def self.logger
|
|
28
|
+
self.new($stderr)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Config
|
|
33
|
+
module DSL
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module Database
|
|
38
|
+
class Base
|
|
39
|
+
def initialize(model, database_id = nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def dump_path; 'dump_path'; end
|
|
43
|
+
def dump_filename; 'dump_filename'; end
|
|
44
|
+
|
|
45
|
+
def log!(*args)
|
|
46
|
+
puts "LOGGING..." if ENV['DEBUG']
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def perform!
|
|
50
|
+
puts "PERFORMING..." if ENV['DEBUG']
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require 'elasticsearch/extensions/backup'
|
|
57
|
+
|
|
58
|
+
class Elasticsearch::Extensions::BackupTest < Elasticsearch::Test::UnitTestCase
|
|
59
|
+
context "The Backup gem extension" do
|
|
60
|
+
setup do
|
|
61
|
+
@model = stub trigger: true
|
|
62
|
+
@subject = ::Backup::Database::Elasticsearch.new(@model)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
should "have a client" do
|
|
66
|
+
assert_instance_of Elasticsearch::Transport::Client, @subject.client
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
should "have a path" do
|
|
70
|
+
assert_instance_of Pathname, @subject.path
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
should "have defaults" do
|
|
74
|
+
assert_equal 'http://localhost:9200', @subject.url
|
|
75
|
+
assert_equal '_all', @subject.indices
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
should "be configurable" do
|
|
79
|
+
@subject = ::Backup::Database::Elasticsearch.new(@model) do |db|
|
|
80
|
+
db.url = 'https://example.com'
|
|
81
|
+
db.indices = 'foo,bar'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
assert_equal 'https://example.com', @subject.url
|
|
85
|
+
assert_equal 'foo,bar', @subject.indices
|
|
86
|
+
|
|
87
|
+
assert_equal 'example.com', @subject.client.transport.connections.first.host[:host]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
should "perform the backup" do
|
|
91
|
+
@subject.expects(:__perform_single)
|
|
92
|
+
@subject.perform!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
should "raise an expection for an unsupported type of backup" do
|
|
96
|
+
@subject = ::Backup::Database::Elasticsearch.new(@model) { |db| db.mode = 'foobar' }
|
|
97
|
+
assert_raise ::Backup::Database::Elasticsearch::Error do
|
|
98
|
+
@subject.perform!
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
should "scan and scroll the index" do
|
|
103
|
+
@subject = ::Backup::Database::Elasticsearch.new(@model) { |db| db.indices = 'test' }
|
|
104
|
+
|
|
105
|
+
@subject.client
|
|
106
|
+
.expects(:search)
|
|
107
|
+
.with do |params|
|
|
108
|
+
assert_equal 'test', params[:index]
|
|
109
|
+
true # Thanks, Ruby 2.2
|
|
110
|
+
end
|
|
111
|
+
.returns({"_scroll_id" => "abc123"})
|
|
112
|
+
|
|
113
|
+
@subject.client
|
|
114
|
+
.expects(:scroll)
|
|
115
|
+
.twice
|
|
116
|
+
.returns({"_scroll_id" => "def456",
|
|
117
|
+
"hits" => { "hits" => [ {"_index"=>"test", "_type"=>"doc", "_id"=>"1", "_source"=>{"title"=>"Test"}} ] }
|
|
118
|
+
})
|
|
119
|
+
.then
|
|
120
|
+
.returns({"_scroll_id" => "ghi789",
|
|
121
|
+
"hits" => { "hits" => [] }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
@subject.__perform_single
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
should "sanitize filename" do
|
|
128
|
+
assert_equal "foo-bar-baz", @subject.__sanitize_filename("foo/bar\nbaz")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'test_helper'
|
|
19
|
+
require 'elasticsearch/extensions/reindex'
|
|
20
|
+
|
|
21
|
+
class Elasticsearch::Extensions::ReindexIntegrationTest < Elasticsearch::Test::IntegrationTestCase
|
|
22
|
+
context "The Reindex extension" do
|
|
23
|
+
setup do
|
|
24
|
+
@port = (ENV['TEST_CLUSTER_PORT'] || 9250).to_i
|
|
25
|
+
|
|
26
|
+
@logger = ::Logger.new(STDERR)
|
|
27
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
28
|
+
color = case severity
|
|
29
|
+
when /INFO/ then :green
|
|
30
|
+
when /ERROR|WARN|FATAL/ then :red
|
|
31
|
+
when /DEBUG/ then :cyan
|
|
32
|
+
else :white
|
|
33
|
+
end
|
|
34
|
+
ANSI.ansi(severity[0] + ' ', color, :faint) + ANSI.ansi(msg, :white, :faint) + "\n"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@client = Elasticsearch::Client.new host: "#{TEST_HOST}:#{TEST_PORT}", logger: @logger
|
|
38
|
+
@client.indices.delete index: '_all'
|
|
39
|
+
|
|
40
|
+
@client.index index: 'test1', type: 'd', id: 1, body: { title: 'TEST 1', category: 'one' }
|
|
41
|
+
@client.index index: 'test1', type: 'd', id: 2, body: { title: 'TEST 2', category: 'two' }
|
|
42
|
+
@client.index index: 'test1', type: 'd', id: 3, body: { title: 'TEST 3', category: 'three' }
|
|
43
|
+
@client.indices.refresh index: 'test1'
|
|
44
|
+
|
|
45
|
+
@client.indices.create index: 'test2'
|
|
46
|
+
|
|
47
|
+
@client.cluster.health wait_for_status: 'yellow'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
teardown do
|
|
51
|
+
@client.indices.delete index: '_all'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
should "copy documents from one index to another" do
|
|
55
|
+
reindex = Elasticsearch::Extensions::Reindex.new \
|
|
56
|
+
source: { index: 'test1', client: @client },
|
|
57
|
+
dest: { index: 'test2' },
|
|
58
|
+
batch_size: 2,
|
|
59
|
+
refresh: true
|
|
60
|
+
|
|
61
|
+
result = reindex.perform
|
|
62
|
+
|
|
63
|
+
assert_equal 0, result[:errors]
|
|
64
|
+
assert_equal 3, @client.search(index: 'test2')['hits']['total']['value']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
should "transform documents with a lambda" do
|
|
68
|
+
reindex = Elasticsearch::Extensions::Reindex.new \
|
|
69
|
+
source: { index: 'test1', client: @client },
|
|
70
|
+
dest: { index: 'test2' },
|
|
71
|
+
transform: lambda { |d| d['_source']['category'].upcase! },
|
|
72
|
+
refresh: true
|
|
73
|
+
|
|
74
|
+
result = reindex.perform
|
|
75
|
+
|
|
76
|
+
assert_equal 0, result[:errors]
|
|
77
|
+
assert_equal 3, @client.search(index: 'test2')['hits']['total']['value']
|
|
78
|
+
assert_equal 'ONE', @client.get(index: 'test2', type: 'd', id: 1)['_source']['category']
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
should "return the number of errors" do
|
|
82
|
+
@client.indices.create index: 'test3', body: { mappings: { properties: { category: { type: 'integer' } }}}
|
|
83
|
+
@client.cluster.health wait_for_status: 'yellow'
|
|
84
|
+
|
|
85
|
+
reindex = Elasticsearch::Extensions::Reindex.new \
|
|
86
|
+
source: { index: 'test1', client: @client },
|
|
87
|
+
dest: { index: 'test3' }
|
|
88
|
+
|
|
89
|
+
result = reindex.perform
|
|
90
|
+
|
|
91
|
+
@client.indices.refresh index: 'test3'
|
|
92
|
+
|
|
93
|
+
assert_equal 3, result[:errors]
|
|
94
|
+
assert_equal 0, @client.search(index: 'test3')['hits']['total']['value']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
should "reindex via the API integration" do
|
|
98
|
+
@client.indices.create index: 'test4'
|
|
99
|
+
|
|
100
|
+
@client.reindex source: { index: 'test1' }, dest: { index: 'test4' }
|
|
101
|
+
@client.indices.refresh index: 'test4'
|
|
102
|
+
|
|
103
|
+
assert_equal 3, @client.search(index: 'test4')['hits']['total']['value']
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
|
3
|
+
# this work for additional information regarding copyright
|
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
|
6
|
+
# not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
|
|
18
|
+
require 'test_helper'
|
|
19
|
+
require 'elasticsearch/extensions/reindex'
|
|
20
|
+
|
|
21
|
+
class Elasticsearch::Extensions::ReindexTest < Elasticsearch::Test::UnitTestCase
|
|
22
|
+
context "The Reindex extension module" do
|
|
23
|
+
DEFAULT_OPTIONS = { source: { index: 'foo', client: Object.new }, dest: { index: 'bar' } }
|
|
24
|
+
|
|
25
|
+
should "require options" do
|
|
26
|
+
assert_raise ArgumentError do
|
|
27
|
+
Elasticsearch::Extensions::Reindex.new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
should "allow to initialize the class" do
|
|
32
|
+
assert_instance_of Elasticsearch::Extensions::Reindex::Reindex,
|
|
33
|
+
Elasticsearch::Extensions::Reindex.new(DEFAULT_OPTIONS)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
should "add the reindex to the API and client" do
|
|
37
|
+
assert_includes Elasticsearch::API::Actions.public_instance_methods.sort, :reindex
|
|
38
|
+
assert_respond_to Elasticsearch::Client.new, :reindex
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
should "pass the client when used in API mode" do
|
|
42
|
+
client = Elasticsearch::Client.new
|
|
43
|
+
|
|
44
|
+
Elasticsearch::Extensions::Reindex::Reindex
|
|
45
|
+
.expects(:new)
|
|
46
|
+
.with({source: { client: client }})
|
|
47
|
+
.returns(stub perform: {})
|
|
48
|
+
|
|
49
|
+
client.reindex
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "when performing the operation" do
|
|
53
|
+
setup do
|
|
54
|
+
d = { '_id' => 'foo', '_type' => 'type', '_source' => { 'foo' => 'bar' } }
|
|
55
|
+
@default_response = { 'hits' => { 'hits' => [d] } }
|
|
56
|
+
@empty_response = { 'hits' => { 'hits' => [] } }
|
|
57
|
+
@bulk_request = [{ index: {
|
|
58
|
+
'_index' => 'bar',
|
|
59
|
+
'_type' => d['_type'],
|
|
60
|
+
'_id' => d['_id'],
|
|
61
|
+
'data' => d['_source']
|
|
62
|
+
} }]
|
|
63
|
+
@bulk_response = {'errors'=>false, 'items' => [{'index' => {}}, {'index' => {}}]}
|
|
64
|
+
@bulk_response_error = {'errors'=>true, 'items' => [{'index' => {}}, {'index' => {'error' => 'FOOBAR'}}]}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
should "scroll through the index and save batches in bulk" do
|
|
68
|
+
client = mock()
|
|
69
|
+
subject = Elasticsearch::Extensions::Reindex.new source: { index: 'foo', client: client },
|
|
70
|
+
dest: { index: 'bar' }
|
|
71
|
+
|
|
72
|
+
client.expects(:search)
|
|
73
|
+
.returns({ '_scroll_id' => 'scroll_id_1' }.merge(Marshal.load(Marshal.dump(@default_response))))
|
|
74
|
+
client.expects(:scroll)
|
|
75
|
+
.returns(Marshal.load(Marshal.dump(@default_response)))
|
|
76
|
+
.then
|
|
77
|
+
.returns(@empty_response).times(2)
|
|
78
|
+
client.expects(:bulk)
|
|
79
|
+
.with(body: @bulk_request)
|
|
80
|
+
.returns(@bulk_response).times(2)
|
|
81
|
+
|
|
82
|
+
result = subject.perform
|
|
83
|
+
|
|
84
|
+
assert_equal 0, result[:errors]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
should "return the number of errors" do
|
|
88
|
+
client = mock()
|
|
89
|
+
subject = Elasticsearch::Extensions::Reindex.new source: { index: 'foo', client: client },
|
|
90
|
+
dest: { index: 'bar' }
|
|
91
|
+
|
|
92
|
+
client.expects(:search).returns({ '_scroll_id' => 'scroll_id_1' }.merge(@default_response))
|
|
93
|
+
client.expects(:scroll).returns(@empty_response)
|
|
94
|
+
client.expects(:bulk).with(body: @bulk_request).returns(@bulk_response_error)
|
|
95
|
+
|
|
96
|
+
result = subject.perform
|
|
97
|
+
|
|
98
|
+
assert_equal 1, result[:errors]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
should "transform the documents with a lambda" do
|
|
102
|
+
client = mock()
|
|
103
|
+
subject = Elasticsearch::Extensions::Reindex.new \
|
|
104
|
+
source: { index: 'foo', client: client },
|
|
105
|
+
dest: { index: 'bar' },
|
|
106
|
+
transform: lambda { |d| d['_source']['foo'].upcase!; d }
|
|
107
|
+
|
|
108
|
+
client.expects(:search).returns({ '_scroll_id' => 'scroll_id_1' }.merge(@default_response))
|
|
109
|
+
client.expects(:scroll).returns(@empty_response)
|
|
110
|
+
client.expects(:bulk).with do |arguments|
|
|
111
|
+
assert_equal 'BAR', arguments[:body][0][:index]['data']['foo']
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
.returns(@bulk_response)
|
|
115
|
+
|
|
116
|
+
result = subject.perform
|
|
117
|
+
|
|
118
|
+
assert_equal 0, result[:errors]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|