solr_cloud-connection 0.6.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/LICENSE.md +27 -0
- data/compose.yml +37 -2
- data/env.test.solr10 +3 -0
- data/lib/solr_cloud/collection.rb +8 -1
- data/lib/solr_cloud/connection/alias_admin.rb +9 -3
- data/lib/solr_cloud/connection/configset_admin.rb +105 -18
- data/lib/solr_cloud/connection/version.rb +1 -1
- data/lib/solr_cloud/connection.rb +11 -0
- data/solr10_compat_update_plan.md +290 -0
- metadata +6 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19bcfd12d8bc2169aad5f3a01163996e671c0e8b2594ad50197ee7127abf15a9
|
|
4
|
+
data.tar.gz: 9088cfcdf21b11fe5ac92fd35ea2d5ec66ea55471d9a0e3a16edf2304db22bf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61d0a5ce50ec30182bc0e60c915a78b6cc492039291634b53e48efb5fd91124643c97ebafc9ad9043d690bf0f28dbd047c5d1688acfb927c5ca126320d9e00c7
|
|
7
|
+
data.tar.gz: 9771f4d1047ef1210eecd1fc78b376363c7d9d7cd34b4c72023eb9f502aa746c1247844de176d03c9c3db8aec9673fa9d65edd97eb06c7f4d9bb18ec7a70a217
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0] - 2026-07-07
|
|
4
|
+
|
|
5
|
+
### Major — Solr 8/9/10 Dual-Version Compatibility
|
|
6
|
+
|
|
7
|
+
This release adds support for Solr 9 and 10 alongside the existing Solr 8 support.
|
|
8
|
+
The internal API dispatch now detects the connected Solr major version and routes
|
|
9
|
+
requests to the correct endpoint, since several Solr APIs changed paths or response
|
|
10
|
+
envelopes between Solr 8 and Solr 9/10.
|
|
11
|
+
|
|
12
|
+
- **Feature: Solr 8/9/10 compatibility**
|
|
13
|
+
- Use V1 `CLUSTERSTATUS` action for collection info instead of V2 `api/collections/{name}`
|
|
14
|
+
(the V2 response envelope changed between Solr 8 and Solr 9/10 — the `cluster` wrapper
|
|
15
|
+
key is absent in Solr 10)
|
|
16
|
+
- Use V1 `LISTALIASES` action with `|| {}` guard for nil aliases on Solr 10
|
|
17
|
+
- Use V1 `admin/configs` endpoint for configset upload (path differs between Solr 8 and 9+)
|
|
18
|
+
- Add `solr9_or_later?` predicate to the connection for external version-gating
|
|
19
|
+
- **CI: Matrix testing** — run tests against Solr 8 & Solr 10 in parallel via GitHub Actions
|
|
20
|
+
- **Infrastructure** — Docker Compose healthchecks, env setup for Solr 10 test container, curl-based wait loops
|
|
21
|
+
|
|
22
|
+
### Chores
|
|
23
|
+
- Replace `wait-for-it` / raw curl loops with Docker Compose healthcheck + `docker compose wait`
|
|
24
|
+
- Add Solr 10 Docker image definition (`spec/data/solr10_docker/`)
|
|
25
|
+
- Update README
|
|
26
|
+
- Version bump to 1.0.0
|
|
27
|
+
|
|
28
|
+
## [0.6.0] - 2025-05-29
|
|
29
|
+
- Expose `username` and `password` getters on the connection
|
|
30
|
+
- Update README with "Common Usage" section
|
|
31
|
+
- Peg dependency versions in gemspec
|
|
32
|
+
- General gemfile/spec cleanup
|
|
33
|
+
|
|
3
34
|
## [0.5.0] - 2025-03-24
|
|
4
35
|
- Added some sugar for unifying aliases and collections
|
|
5
36
|
- Update tests
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2026, The Regents of the University of Michigan.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are
|
|
6
|
+
met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
* Neither the name of the The University of Michigan nor the
|
|
14
|
+
names of its contributors may be used to endorse or promote products
|
|
15
|
+
derived from this software without specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS OF THE UNIVERSITY OF MICHIGAN AND
|
|
18
|
+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
|
19
|
+
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
20
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE
|
|
21
|
+
UNIVERSITY OF MICHIGAN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
22
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
23
|
+
TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
24
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
25
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
26
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
27
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/compose.yml
CHANGED
|
@@ -11,21 +11,56 @@ services:
|
|
|
11
11
|
- .env
|
|
12
12
|
- env.test
|
|
13
13
|
command: "tail -f /dev/null"
|
|
14
|
+
depends_on:
|
|
15
|
+
solr:
|
|
16
|
+
condition: service_healthy
|
|
17
|
+
solr10:
|
|
18
|
+
condition: service_healthy
|
|
14
19
|
|
|
15
20
|
solr:
|
|
16
21
|
build: spec/data/solr_docker/.
|
|
17
22
|
ports:
|
|
18
|
-
- "
|
|
23
|
+
- "6781:8983"
|
|
19
24
|
environment:
|
|
20
25
|
- ZK_HOST=zoo:2181
|
|
21
26
|
depends_on:
|
|
22
27
|
- zoo
|
|
23
28
|
command: solr-foreground
|
|
29
|
+
healthcheck:
|
|
30
|
+
test: ["CMD-SHELL", "curl -sf -u solr:SolrRocks http://localhost:8983/solr/admin/info/system || exit 1"]
|
|
31
|
+
interval: 10s
|
|
32
|
+
timeout: 5s
|
|
33
|
+
retries: 12
|
|
34
|
+
start_period: 30s
|
|
24
35
|
|
|
25
36
|
zoo:
|
|
26
37
|
image: zookeeper
|
|
27
38
|
ports:
|
|
28
|
-
-
|
|
39
|
+
- 6782:2181
|
|
40
|
+
environment:
|
|
41
|
+
ZOO_MY_ID: 1
|
|
42
|
+
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181
|
|
43
|
+
|
|
44
|
+
solr10:
|
|
45
|
+
build: spec/data/solr10_docker/.
|
|
46
|
+
ports:
|
|
47
|
+
- "6783:8983"
|
|
48
|
+
environment:
|
|
49
|
+
- ZK_HOST=zoo10:2181
|
|
50
|
+
depends_on:
|
|
51
|
+
- zoo10
|
|
52
|
+
command: solr-foreground
|
|
53
|
+
healthcheck:
|
|
54
|
+
test: ["CMD-SHELL", "curl -sf -u solr:SolrRocks http://localhost:8983/solr/admin/info/system || exit 1"]
|
|
55
|
+
interval: 10s
|
|
56
|
+
timeout: 5s
|
|
57
|
+
retries: 12
|
|
58
|
+
start_period: 30s
|
|
59
|
+
|
|
60
|
+
zoo10:
|
|
61
|
+
image: zookeeper
|
|
62
|
+
ports:
|
|
63
|
+
- 6784:2181
|
|
29
64
|
environment:
|
|
30
65
|
ZOO_MY_ID: 1
|
|
31
66
|
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181
|
data/env.test.solr10
ADDED
|
@@ -98,8 +98,15 @@ module SolrCloud
|
|
|
98
98
|
|
|
99
99
|
# Access to the root info from the api. Mostly for internal use, but not labeled as such
|
|
100
100
|
# 'cause users will almost certainly find a use for it.
|
|
101
|
+
#
|
|
102
|
+
# Uses the V1 +CLUSTERSTATUS+ action rather than the V2 +api/collections/{name}+
|
|
103
|
+
# endpoint because the V2 response envelope changed between Solr 8 and Solr 9/10
|
|
104
|
+
# (the +cluster+ wrapper key is absent in Solr 10). The V1 CLUSTERSTATUS response
|
|
105
|
+
# consistently returns +cluster.collections.{name}+ on all supported versions.
|
|
106
|
+
#
|
|
107
|
+
# @return [Hash] the collection status hash as returned by Solr
|
|
101
108
|
def info
|
|
102
|
-
connection.get("
|
|
109
|
+
connection.get("solr/admin/collections", action: "CLUSTERSTATUS", collection: name).body["cluster"]["collections"][name]
|
|
103
110
|
end
|
|
104
111
|
|
|
105
112
|
# Reported as healthy?
|
|
@@ -74,10 +74,16 @@ module SolrCloud
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
# The "raw" alias map, which just maps alias names to collection names
|
|
78
|
-
#
|
|
77
|
+
# The "raw" alias map, which just maps alias names to collection names.
|
|
78
|
+
#
|
|
79
|
+
# The Solr +LISTALIASES+ action omits the +"aliases"+ key entirely (returning +null+
|
|
80
|
+
# or an absent key) when no aliases are defined. This is true on Solr 10 and can
|
|
81
|
+
# occur on earlier versions as well. The +|| {}+ guard ensures callers always receive
|
|
82
|
+
# a Hash rather than +nil+.
|
|
83
|
+
#
|
|
84
|
+
# @return [Hash<String, String>] map of alias name -> collection name, empty if none exist
|
|
79
85
|
def raw_alias_map
|
|
80
|
-
connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"]
|
|
86
|
+
connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"] || {}
|
|
81
87
|
end
|
|
82
88
|
end
|
|
83
89
|
end
|
|
@@ -4,15 +4,30 @@ require "zip"
|
|
|
4
4
|
|
|
5
5
|
module SolrCloud
|
|
6
6
|
class Connection
|
|
7
|
-
#
|
|
7
|
+
# Methods having to do with configsets, to be included by the connection object.
|
|
8
8
|
# These are split out only to make it easier to deal with them.
|
|
9
9
|
module ConfigsetAdmin
|
|
10
10
|
# Given the path to a solr configuration "conf" directory (i.e., the one with
|
|
11
11
|
# solrconfig.xml in it), zip it up and send it to solr as a new configset.
|
|
12
|
+
#
|
|
13
|
+
# The upload API endpoint differs across major Solr versions:
|
|
14
|
+
#
|
|
15
|
+
# - *Solr 8*: uses the V2 API (+PUT /api/cluster/configs/<name>+), which was the
|
|
16
|
+
# canonical configset path in Solr 8.
|
|
17
|
+
# - *Solr 9+*: uses the V2 API (+PUT /api/configsets/<name>+).
|
|
18
|
+
# The V2 configset path moved from +/api/cluster/configs/<name>+ (Solr 8) to
|
|
19
|
+
# +/api/configsets/<name>+ (Solr 9+). The new V2 PUT defaults to overwriting an
|
|
20
|
+
# existing configset, which means in-use configsets can be replaced without
|
|
21
|
+
# deleting them first.
|
|
22
|
+
#
|
|
23
|
+
# Dispatches via {Connection#solr9_or_later?}; see {#upload_configset_v9} and
|
|
24
|
+
# {#upload_configset_v8} for the version-specific implementations.
|
|
25
|
+
#
|
|
12
26
|
# @param name [String] Name to give the new configset
|
|
13
27
|
# @param confdir [String, Pathname] Path to the solr configuration "conf" directory
|
|
14
28
|
# @param force [Boolean] Whether or not to overwrite an existing configset if there is one
|
|
15
|
-
# @raise [
|
|
29
|
+
# @raise [IllegalNameError] if +name+ is not a valid Solr identifier
|
|
30
|
+
# @raise [WontOverwriteError] if the configset already exists and +force+ is false
|
|
16
31
|
# @return [Configset] the configset created
|
|
17
32
|
def create_configset(name:, confdir:, force: false)
|
|
18
33
|
config_set_name = name
|
|
@@ -23,36 +38,58 @@ module SolrCloud
|
|
|
23
38
|
if has_configset?(config_set_name) && !force
|
|
24
39
|
raise WontOverwriteError.new("Won't replace configset #{config_set_name} unless 'force: true' passed ")
|
|
25
40
|
end
|
|
41
|
+
|
|
26
42
|
zfile = "#{Dir.tmpdir}/solr_add_configset_#{name}_#{Time.now.hash}.zip"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
ZipFileGenerator.new(confdir, zfile).write
|
|
44
|
+
|
|
45
|
+
if solr9_or_later?
|
|
46
|
+
upload_configset_v9(config_set_name, zfile)
|
|
47
|
+
else
|
|
48
|
+
upload_configset_v8(config_set_name, zfile)
|
|
31
49
|
end
|
|
32
|
-
|
|
33
|
-
FileUtils.rm(zfile, force: true)
|
|
50
|
+
|
|
34
51
|
get_configset(name)
|
|
52
|
+
ensure
|
|
53
|
+
FileUtils.rm(zfile, force: true) if zfile
|
|
35
54
|
end
|
|
36
55
|
|
|
37
|
-
# Get a list of the already-defined
|
|
38
|
-
# @return [Array<Configset>] possibly empty list of
|
|
56
|
+
# Get a list of the already-defined configsets.
|
|
57
|
+
# @return [Array<Configset>] possibly empty list of configsets
|
|
39
58
|
def configsets
|
|
40
59
|
configset_names.map { |cs| Configset.new(name: cs, connection: self) }
|
|
41
60
|
end
|
|
42
61
|
|
|
62
|
+
# Get the names of all defined configsets.
|
|
63
|
+
#
|
|
64
|
+
# The API endpoint differs across major Solr versions:
|
|
65
|
+
#
|
|
66
|
+
# - *Solr 8*: uses the V2 API (+GET /api/cluster/configs+).
|
|
67
|
+
# - *Solr 9+*: uses the V1 API (+GET /solr/admin/configs?action=LIST+).
|
|
68
|
+
# The V2 configset path changed between Solr 8 and Solr 9; V1 is used for
|
|
69
|
+
# consistency on Solr 9 and later.
|
|
70
|
+
#
|
|
43
71
|
# @return [Array<String>] the names of the config sets
|
|
44
72
|
def configset_names
|
|
45
|
-
|
|
73
|
+
if solr9_or_later?
|
|
74
|
+
connection.get("solr/admin/configs", action: "LIST").body["configSets"]
|
|
75
|
+
else
|
|
76
|
+
connection.get("api/cluster/configs").body["configSets"]
|
|
77
|
+
end
|
|
46
78
|
end
|
|
47
79
|
|
|
48
|
-
# Check to see if a configset is defined
|
|
49
|
-
# @param name [String] Name of the
|
|
80
|
+
# Check to see if a configset with the given name is defined.
|
|
81
|
+
# @param name [String] Name of the configset
|
|
50
82
|
# @return [Boolean] Whether a configset with that name exists
|
|
51
83
|
def has_configset?(name)
|
|
52
84
|
configset_names.include? name.to_s
|
|
53
85
|
end
|
|
54
86
|
|
|
55
|
-
# Get an existing configset
|
|
87
|
+
# Get an existing configset by name.
|
|
88
|
+
#
|
|
89
|
+
# @note Does not verify that the configset actually exists; use {#has_configset?}
|
|
90
|
+
# to check first if needed.
|
|
91
|
+
# @param name [String] Name of the configset
|
|
92
|
+
# @return [Configset]
|
|
56
93
|
def get_configset(name)
|
|
57
94
|
Configset.new(name: name, connection: self)
|
|
58
95
|
end
|
|
@@ -63,12 +100,25 @@ module SolrCloud
|
|
|
63
100
|
#
|
|
64
101
|
# In general, prefer using {Configset#delete!} instead of running everything
|
|
65
102
|
# through the connection object.
|
|
66
|
-
#
|
|
67
|
-
#
|
|
103
|
+
#
|
|
104
|
+
# The delete API endpoint differs across major Solr versions:
|
|
105
|
+
#
|
|
106
|
+
# - *Solr 8*: uses the V2 API (+DELETE /api/cluster/configs/<name>+).
|
|
107
|
+
# - *Solr 9+*: uses the V1 API (+GET /solr/admin/configs?action=DELETE+).
|
|
108
|
+
# The V2 configset path changed between Solr 8 and Solr 9; V1 is used for
|
|
109
|
+
# consistency on Solr 9 and later.
|
|
110
|
+
#
|
|
111
|
+
# @param name [String] The name of the configuration set
|
|
112
|
+
# @raise [ConfigSetInUseError] if the configset can't be deleted because it's in use
|
|
113
|
+
# by a live collection
|
|
68
114
|
# @return [Connection] self
|
|
69
115
|
def delete_configset(name)
|
|
70
|
-
if has_configset?
|
|
71
|
-
|
|
116
|
+
if has_configset?(name)
|
|
117
|
+
if solr9_or_later?
|
|
118
|
+
connection.get("solr/admin/configs", action: "DELETE", name: name)
|
|
119
|
+
else
|
|
120
|
+
connection.delete("api/cluster/configs/#{name}")
|
|
121
|
+
end
|
|
72
122
|
end
|
|
73
123
|
self
|
|
74
124
|
rescue Faraday::BadRequestError => e
|
|
@@ -125,6 +175,43 @@ module SolrCloud
|
|
|
125
175
|
zipfile.add(zipfile_path, disk_file_path)
|
|
126
176
|
end
|
|
127
177
|
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
# Upload a configset zip using the Solr 9+ V2 API.
|
|
182
|
+
#
|
|
183
|
+
# Sends a +PUT+ to +/api/configsets/<name>+ with the zip file as an
|
|
184
|
+
# octet-stream body. The V2 configset upload path moved from
|
|
185
|
+
# +/api/cluster/configs/<name>+ (Solr 8) to +/api/configsets/<name>+ (Solr 9+).
|
|
186
|
+
# The new V2 PUT defaults to overwriting any existing configset at that name,
|
|
187
|
+
# so in-use configsets can be replaced without deleting first.
|
|
188
|
+
#
|
|
189
|
+
# @param name [String] configset name
|
|
190
|
+
# @param zipfile_path [String] filesystem path to the zip archive to upload
|
|
191
|
+
# @return [void]
|
|
192
|
+
def upload_configset_v9(name, zipfile_path)
|
|
193
|
+
connection.put("api/configsets/#{name}") do |req|
|
|
194
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
195
|
+
req.body = File.binread(zipfile_path)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Upload a configset zip using the Solr 8 V2 API.
|
|
200
|
+
#
|
|
201
|
+
# Sends a +PUT+ to +/api/cluster/configs/<name>+ with the zip file as an
|
|
202
|
+
# octet-stream body. This was the canonical V2 configset upload path in Solr 8.
|
|
203
|
+
# The path changed to +/api/configsets/<name>+ in Solr 9; use {#upload_configset_v1}
|
|
204
|
+
# for Solr 9 and later.
|
|
205
|
+
#
|
|
206
|
+
# @param name [String] configset name
|
|
207
|
+
# @param zipfile_path [String] filesystem path to the zip archive to upload
|
|
208
|
+
# @return [void]
|
|
209
|
+
def upload_configset_v8(name, zipfile_path)
|
|
210
|
+
connection.put("api/cluster/configs/#{name}") do |req|
|
|
211
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
212
|
+
req.body = File.binread(zipfile_path)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
128
215
|
end
|
|
129
216
|
end
|
|
130
217
|
end
|
|
@@ -176,6 +176,17 @@ module SolrCloud
|
|
|
176
176
|
_version_part_int(2)
|
|
177
177
|
end
|
|
178
178
|
|
|
179
|
+
# Returns true if the connected Solr instance is version 9 or later (9, 10, 11, ...).
|
|
180
|
+
#
|
|
181
|
+
# Several Solr APIs changed paths between Solr 8 and Solr 9. This predicate is used
|
|
182
|
+
# internally to dispatch to the correct API endpoint when behavior differs across major
|
|
183
|
+
# versions. External callers may also use it to gate version-specific logic.
|
|
184
|
+
#
|
|
185
|
+
# @return [Boolean] true if the server reports major version >= 9
|
|
186
|
+
def solr9_or_later?
|
|
187
|
+
major_version >= 9
|
|
188
|
+
end
|
|
189
|
+
|
|
179
190
|
# Check to see if the given string follows solr's rules for thing
|
|
180
191
|
# Solr only allows ASCII letters and numbers, underscore, and dash,
|
|
181
192
|
# and it can't start with an underscore.
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Solr 8 / 9 / 10 Compatibility Update Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Make `solr_cloud-connection` work correctly with Solr 8, 9, and 10 simultaneously. The approach:
|
|
6
|
+
branch on `major_version` where APIs differ, and use V1 (legacy) APIs where they work uniformly
|
|
7
|
+
across versions.
|
|
8
|
+
|
|
9
|
+
Version detection is already in place in `connection.rb` via `major_version`, `minor_version`,
|
|
10
|
+
and `patch_version` methods derived from `GET /solr/admin/info/system`.
|
|
11
|
+
|
|
12
|
+
Source examined: `main` branch and `fix/solr-10-api-compatibility` branch.
|
|
13
|
+
API docs source: Apache Solr Reference Guide 10.0 (live, fetched May 2026).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Root Cause
|
|
18
|
+
|
|
19
|
+
The main branch uses Solr 8-era V2 API paths for configset management
|
|
20
|
+
(`api/cluster/configs/*`). These paths changed in Solr 9/10 to `api/configsets/*`.
|
|
21
|
+
Additionally, the V1 `LISTALIASES` action returns `null` (not `{}`) in Solr 10 when
|
|
22
|
+
no aliases exist, causing a `NoMethodError` on `nil.keys`.
|
|
23
|
+
|
|
24
|
+
The `fix/solr-10-api-compatibility` branch already addresses this by switching configset
|
|
25
|
+
operations wholesale to V1 APIs and adding a `|| {}` nil guard, but it does so without
|
|
26
|
+
version branching. This plan implements proper version-based dispatch.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## API Compatibility Matrix
|
|
31
|
+
|
|
32
|
+
| Method / File | Solr 8 (main branch) | Solr 9/10 compat? | Notes |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| `configset_admin#create_configset` | `PUT api/cluster/configs/{name}` (V2) | NO | V2 path changed |
|
|
35
|
+
| `configset_admin#configset_names` | `GET api/cluster/configs` (V2) | NO | V2 path changed |
|
|
36
|
+
| `configset_admin#delete_configset` | `DELETE api/cluster/configs/{name}` (V2) | NO | V2 path changed |
|
|
37
|
+
| `alias_admin#raw_alias_map` | `GET solr/admin/collections?action=LISTALIASES` | PARTIAL | returns nil when empty |
|
|
38
|
+
| `alias_admin#create_alias` | V1 CREATEALIAS | YES | V1 works in all versions |
|
|
39
|
+
| `alias#delete!` | V1 DELETEALIAS | YES | V1 works in all versions |
|
|
40
|
+
| `collection_admin#only_collections` | `GET api/collections` (V2) | YES | same response format |
|
|
41
|
+
| `collection_admin#create_collection` | V1 CREATE | YES | V1 works in all versions |
|
|
42
|
+
| `collection#delete!` | V1 DELETE | YES | V1 works in all versions |
|
|
43
|
+
| `collection#info` | `GET api/collections/{name}` (V2) | UNKNOWN | needs testing; see below |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Changes Required
|
|
48
|
+
|
|
49
|
+
### 1. `connection.rb` -- add version-helper predicate
|
|
50
|
+
|
|
51
|
+
Add a convenience predicate to avoid repeating `major_version >= 9` comparisons inline:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# Returns true for Solr 9 and later (including 10, 11, ...)
|
|
55
|
+
def solr9_or_later?
|
|
56
|
+
major_version >= 9
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Place it alongside the existing `major_version` / `minor_version` / `patch_version` methods.
|
|
61
|
+
No changes to `bail_if_incompatible!`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 2. `connection/configset_admin.rb` -- version-branch all three methods
|
|
66
|
+
|
|
67
|
+
The V2 configset paths changed between Solr 8 and Solr 9:
|
|
68
|
+
|
|
69
|
+
| Operation | Solr 8 V2 path | Solr 9/10 V2 path | V1 path (all versions) |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| Upload | `PUT api/cluster/configs/{name}` | `PUT api/configsets/{name}` | `POST solr/admin/configs?action=UPLOAD&name={name}` |
|
|
72
|
+
| List | `GET api/cluster/configs` -> `["configSets"]` | `GET api/configsets` -> `["configSets"]` | `GET solr/admin/configs?action=LIST` -> `["configSets"]` |
|
|
73
|
+
| Delete | `DELETE api/cluster/configs/{name}` | `DELETE api/configsets/{name}` | `GET solr/admin/configs?action=DELETE&name={name}` |
|
|
74
|
+
|
|
75
|
+
Strategy: use V1 for Solr 9+, keep existing V2 for Solr 8. V1 works in Solr 8 too, so
|
|
76
|
+
an alternative is to use V1 for all versions and avoid branching entirely -- but the task
|
|
77
|
+
requires explicit version dispatch.
|
|
78
|
+
|
|
79
|
+
#### `create_configset`
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
def create_configset(name:, confdir:, force: false)
|
|
83
|
+
raise WontOverwriteError.new("Configset '#{name}' already exists") if has_configset?(name) && !force
|
|
84
|
+
zipfile = zip_configset_dir(confdir)
|
|
85
|
+
if connection.solr9_or_later?
|
|
86
|
+
connection.post("solr/admin/configs?action=UPLOAD&name=#{name}") do |req|
|
|
87
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
88
|
+
req.body = File.binread(zipfile)
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
connection.put("api/cluster/configs/#{name}") do |req|
|
|
92
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
93
|
+
req.body = File.binread(zipfile)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
get_configset(name)
|
|
97
|
+
ensure
|
|
98
|
+
FileUtils.rm_f(zipfile) if zipfile
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Note: the `fix` branch uses `connection.post("solr/admin/configs", action: "UPLOAD",
|
|
103
|
+
name: name)` but that places action/name as query params on a POST. The Solr V1 API
|
|
104
|
+
docs specify these as query string parameters for the UPLOAD action, so either form works.
|
|
105
|
+
Verify with integration tests.
|
|
106
|
+
|
|
107
|
+
#### `configset_names`
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
def configset_names
|
|
111
|
+
if connection.solr9_or_later?
|
|
112
|
+
connection.get("solr/admin/configs", action: "LIST").body["configSets"]
|
|
113
|
+
else
|
|
114
|
+
connection.get("api/cluster/configs").body["configSets"]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `delete_configset`
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
def delete_configset(name:)
|
|
123
|
+
return unless has_configset?(name)
|
|
124
|
+
if connection.solr9_or_later?
|
|
125
|
+
connection.get("solr/admin/configs", action: "DELETE", name: name)
|
|
126
|
+
else
|
|
127
|
+
connection.delete("api/cluster/configs/#{name}")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 3. `connection/alias_admin.rb` -- nil guard (bug fix, not version-specific)
|
|
135
|
+
|
|
136
|
+
`raw_alias_map` crashes with `NoMethodError` when no aliases exist because the Solr
|
|
137
|
+
`LISTALIASES` response omits the `aliases` key entirely (returns `null` or simply absent).
|
|
138
|
+
This is confirmed on Solr 10 and the fix is straightforward:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
def raw_alias_map
|
|
142
|
+
connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"] || {}
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This fix is already present in `fix/solr-10-api-compatibility`. It is not version-specific --
|
|
147
|
+
apply it unconditionally, as it is defensive coding.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 4. `lib/solr_cloud/collection.rb` -- verify `info` endpoint (needs testing)
|
|
152
|
+
|
|
153
|
+
`Collection#info` calls:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The response path `body["cluster"]["collections"][name]` matches the structure returned by
|
|
160
|
+
the V1 `CLUSTERSTATUS` API and the V2 `GET /api/cluster` endpoint. Whether
|
|
161
|
+
`GET /api/collections/{name}` returns this same nested structure in Solr 9/10 is not
|
|
162
|
+
confirmed from docs alone.
|
|
163
|
+
|
|
164
|
+
**Action required:**
|
|
165
|
+
- Run integration tests against Solr 9 and Solr 10 containers with the existing code
|
|
166
|
+
- If `api/collections/{name}` returns a different response structure in Solr 9/10,
|
|
167
|
+
add version branching:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
def info
|
|
171
|
+
if connection.solr9_or_later?
|
|
172
|
+
# Fallback to V1 CLUSTERSTATUS filtered to this collection
|
|
173
|
+
connection.get("solr/admin/collections",
|
|
174
|
+
action: "CLUSTERSTATUS",
|
|
175
|
+
collection: name
|
|
176
|
+
).body["cluster"]["collections"][name]
|
|
177
|
+
else
|
|
178
|
+
connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
V1 `CLUSTERSTATUS` with `collection` param is confirmed to work in both Solr 8 and 10
|
|
184
|
+
and returns the exact same `cluster.collections.{name}` structure.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### 5. No changes needed
|
|
189
|
+
|
|
190
|
+
The following use V1 APIs or V2 APIs that are stable across Solr 8, 9, and 10:
|
|
191
|
+
|
|
192
|
+
- `alias_admin#create_alias` -- V1 `CREATEALIAS`
|
|
193
|
+
- `alias#delete!` -- V1 `DELETEALIAS`
|
|
194
|
+
- `collection_admin#create_collection` -- V1 `CREATE`
|
|
195
|
+
- `collection_admin#only_collections` -- `GET api/collections` response format confirmed
|
|
196
|
+
unchanged in Solr 10 (`{"collections": [...]}`)
|
|
197
|
+
- `collection#delete!` -- V1 `DELETE`
|
|
198
|
+
- `connection#system` / `version_string` / `major_version` etc. -- `/solr/admin/info/system`
|
|
199
|
+
unchanged
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Implementation Pattern
|
|
204
|
+
|
|
205
|
+
Use private dispatch methods to keep the public interface clean:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# In ConfigsetAdmin module:
|
|
209
|
+
|
|
210
|
+
def create_configset(name:, confdir:, force: false)
|
|
211
|
+
raise WontOverwriteError.new(...) if has_configset?(name) && !force
|
|
212
|
+
zipfile = zip_configset_dir(confdir)
|
|
213
|
+
solr9_or_later? ? upload_configset_v1(name, zipfile) : upload_configset_v8(name, zipfile)
|
|
214
|
+
get_configset(name)
|
|
215
|
+
ensure
|
|
216
|
+
FileUtils.rm_f(zipfile) if zipfile
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def upload_configset_v1(name, zipfile)
|
|
222
|
+
connection.post("solr/admin/configs?action=UPLOAD&name=#{name}") do |req|
|
|
223
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
224
|
+
req.body = File.binread(zipfile)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def upload_configset_v8(name, zipfile)
|
|
229
|
+
connection.put("api/cluster/configs/#{name}") do |req|
|
|
230
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
|
231
|
+
req.body = File.binread(zipfile)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Where the dispatch is simple (one line each branch), inline `if/else` is acceptable.
|
|
237
|
+
Prefer private methods when the per-version logic is more than 2 lines.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Test Infrastructure
|
|
242
|
+
|
|
243
|
+
The existing spec infrastructure uses Docker Compose in `spec/data/solr_docker/`.
|
|
244
|
+
|
|
245
|
+
Add a Solr 10 service (and optionally Solr 9) alongside the existing Solr 8 service:
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
# In spec/data/solr_docker/docker-compose.yml (new service):
|
|
249
|
+
solr10:
|
|
250
|
+
image: solr:10-slim
|
|
251
|
+
ports:
|
|
252
|
+
- "18984:8983"
|
|
253
|
+
command: solr-demo
|
|
254
|
+
# or whatever start command the existing solr8 service uses
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Update the integration test runner (likely a Rake task or script) to run the full spec
|
|
258
|
+
suite twice: once against Solr 8 and once against Solr 10 (passing the base URL as an
|
|
259
|
+
environment variable).
|
|
260
|
+
|
|
261
|
+
If the test suite already accepts a `SOLR_URL` environment variable, this is straightforward.
|
|
262
|
+
If it hardcodes a port, parameterize it.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Implementation Order
|
|
267
|
+
|
|
268
|
+
1. Add `solr9_or_later?` to `connection.rb`
|
|
269
|
+
2. Apply `|| {}` nil guard to `alias_admin.rb#raw_alias_map` (already in fix branch --
|
|
270
|
+
cherry-pick or re-apply)
|
|
271
|
+
3. Version-branch `configset_admin.rb`: `configset_names`, `delete_configset`,
|
|
272
|
+
`create_configset` (in that order -- names and delete first so tests can verify the
|
|
273
|
+
cycle without broken upload)
|
|
274
|
+
4. Add Solr 10 container to Docker Compose test setup
|
|
275
|
+
5. Run integration tests against both Solr 8 and Solr 10
|
|
276
|
+
6. If `Collection#info` fails against Solr 10, add version-branch (step 4 above)
|
|
277
|
+
7. Update gem version (patch or minor, depending on whether the nil-guard fix is considered
|
|
278
|
+
a breaking change)
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Notes on the fix/solr-10-api-compatibility Branch
|
|
283
|
+
|
|
284
|
+
That branch's approach (switch all configset ops to V1 unconditionally) is functionally
|
|
285
|
+
correct and simpler. If backward compatibility with the V2 Solr 8 behavior is not a concern,
|
|
286
|
+
that approach is acceptable and lower-risk than version branching. The user has explicitly
|
|
287
|
+
requested version-based dispatch, which this plan implements.
|
|
288
|
+
|
|
289
|
+
If Solr 8 V2 API support is dropped in a future major version of the gem, the branches
|
|
290
|
+
can be collapsed to use V1 everywhere.
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solr_cloud-connection
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bill Dueber
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: faraday
|
|
@@ -154,7 +153,6 @@ dependencies:
|
|
|
154
153
|
- - "~>"
|
|
155
154
|
- !ruby/object:Gem::Version
|
|
156
155
|
version: '3.0'
|
|
157
|
-
description:
|
|
158
156
|
email:
|
|
159
157
|
- bill@dueber.com
|
|
160
158
|
executables: []
|
|
@@ -166,11 +164,13 @@ files:
|
|
|
166
164
|
- ".standard.yml"
|
|
167
165
|
- CHANGELOG.md
|
|
168
166
|
- Dockerfile
|
|
167
|
+
- LICENSE.md
|
|
169
168
|
- LICENSE.txt
|
|
170
169
|
- README.md
|
|
171
170
|
- Rakefile
|
|
172
171
|
- compose.yml
|
|
173
172
|
- env.test
|
|
173
|
+
- env.test.solr10
|
|
174
174
|
- lib/solr_cloud/alias.rb
|
|
175
175
|
- lib/solr_cloud/collection.rb
|
|
176
176
|
- lib/solr_cloud/configset.rb
|
|
@@ -181,13 +181,13 @@ files:
|
|
|
181
181
|
- lib/solr_cloud/connection/version.rb
|
|
182
182
|
- lib/solr_cloud/errors.rb
|
|
183
183
|
- readme.rb
|
|
184
|
+
- solr10_compat_update_plan.md
|
|
184
185
|
homepage: https://github.com/mlibrary/solr_cloud-connection
|
|
185
186
|
licenses: []
|
|
186
187
|
metadata:
|
|
187
188
|
homepage_uri: https://github.com/mlibrary/solr_cloud-connection
|
|
188
189
|
source_code_uri: https://github.com/mlibrary/solr_cloud-connection
|
|
189
190
|
changelog_uri: https://github.com/mlibrary/solr_cloud-connection/CHANGELOG.md
|
|
190
|
-
post_install_message:
|
|
191
191
|
rdoc_options: []
|
|
192
192
|
require_paths:
|
|
193
193
|
- lib
|
|
@@ -202,8 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
202
202
|
- !ruby/object:Gem::Version
|
|
203
203
|
version: '0'
|
|
204
204
|
requirements: []
|
|
205
|
-
rubygems_version: 3.
|
|
206
|
-
signing_key:
|
|
205
|
+
rubygems_version: 3.6.7
|
|
207
206
|
specification_version: 4
|
|
208
207
|
summary: Do basic administrative operations on a solr cloud instance and collections
|
|
209
208
|
within
|