Skip to content

Commit e2bf1f4

Browse files
committed
SOLR-17098: Only use ZK ACLs for default ZK Host
1 parent 65920af commit e2bf1f4

File tree

14 files changed

+190
-20
lines changed

14 files changed

+190
-20
lines changed

solr/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ Bug Fixes
158158

159159
* SOLR-17060: CoreContainer#create may deadlock with concurrent requests for metrics (Alex Deparvu, David Smiley)
160160

161+
* SOLR-17098: ZK Credentials and ACLs are no longer sent to all ZK Servers when using Streaming Expressions.
162+
They will only be used when sent to the default ZK Host. (Houston Putman, Jan Høydahl, David Smiley, Gus Heck, Qing Xu)
163+
161164
Dependency Upgrades
162165
---------------------
163166
* SOLR-17012: Update Apache Hadoop to 3.3.6 and Apache Curator to 5.5.0 (Kevin Risden)

solr/core/src/java/org/apache/solr/core/CoreContainer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ private void loadInternal() {
829829

830830
zkSys.initZooKeeper(this, cfg.getCloudConfig());
831831
if (isZooKeeperAware()) {
832+
solrClientCache.setDefaultZKHost(getZkController().getZkServerAddress());
832833
// initialize ZkClient metrics
833834
zkSys.getZkMetricsProducer().initializeMetrics(solrMetricsContext, "zkClient");
834835
pkiAuthenticationSecurityBuilder =

solr/solr-ref-guide/modules/query-guide/pages/stream-decorator-reference.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ Worker collections can be empty collections that exist only to execute streaming
11871187
* `StreamExpression`: Expression to send to the worker collection.
11881188
* `workers`: Number of workers in the worker collection to send the expression to.
11891189
* `zkHost`: (Optional) The ZooKeeper connect string where the worker collection resides.
1190+
Zookeeper Credentials and ACLs will only be included if the same ZkHost is used as the Solr instance that you are connecting to (the `chroot` can be different).
11901191
* `sort`: The sort criteria for ordering tuples returned by the worker nodes.
11911192

11921193
=== parallel Syntax

solr/solr-ref-guide/modules/query-guide/pages/stream-source-reference.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ To read more about the `/export` handler requirements review the section xref:ex
3636
* `fl`: (Mandatory) The list of fields to return.
3737
* `sort`: (Mandatory) The sort criteria.
3838
* `zkHost`: Only needs to be defined if the collection being searched is found in a different zkHost than the local stream handler.
39+
Zookeeper Credentials and ACLs will only be included if the same ZkHost is used as the Solr instance that you are connecting to (the `chroot` can be different).
3940
* `qt`: Specifies the query type, or request handler, to use.
4041
Set this to `/export` to work with large result sets.
4142
The default is `/select`.
@@ -484,6 +485,7 @@ When used in parallel mode the partitionKeys parameter must be provided.
484485
* `fl`: (Mandatory) The list of fields to return.
485486
* `sort`: (Mandatory) The sort criteria.
486487
* `zkHost`: Only needs to be defined if the collection being searched is found in a different zkHost than the local stream handler.
488+
Zookeeper Credentials and ACLs will only be included if the same ZkHost is used as the Solr instance that you are connecting to (the `chroot` can be different).
487489
* `partitionKeys`: Comma delimited list of keys to partition the search results by.
488490
To be used with the parallel function for parallelizing operations across worker nodes.
489491
See the xref:stream-decorator-reference.adoc#parallel[parallel] function for details.
@@ -648,6 +650,7 @@ The checkpoints will be saved under this id.
648650
If not set, it defaults to the highest version in the index.
649651
Setting to 0 will process all records that match query in the index.
650652
* `zkHost`: (Optional) Only needs to be defined if the collection being searched is found in a different zkHost than the local stream handler.
653+
Zookeeper Credentials and ACLs will only be included if the same ZkHost is used as the Solr instance that you are connecting to (the `chroot` can be different).
651654

652655
=== topic Syntax
653656

solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Optional;
2626
import java.util.concurrent.TimeUnit;
2727
import java.util.concurrent.atomic.AtomicBoolean;
28+
import java.util.concurrent.atomic.AtomicReference;
2829
import org.apache.http.client.HttpClient;
2930
import org.apache.solr.client.solrj.SolrClient;
3031
import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
@@ -57,6 +58,7 @@ public class SolrClientCache implements Closeable {
5758
private final HttpClient apacheHttpClient;
5859
private final Http2SolrClient http2SolrClient;
5960
private final AtomicBoolean isClosed = new AtomicBoolean(false);
61+
private final AtomicReference<String> defaultZkHost = new AtomicReference<>();
6062

6163
public SolrClientCache() {
6264
this.apacheHttpClient = null;
@@ -74,40 +76,71 @@ public SolrClientCache(Http2SolrClient http2SolrClient) {
7476
this.http2SolrClient = http2SolrClient;
7577
}
7678

79+
public void setDefaultZKHost(String zkHost) {
80+
if (zkHost != null) {
81+
zkHost = zkHost.split("/")[0];
82+
if (!zkHost.isEmpty()) {
83+
defaultZkHost.set(zkHost);
84+
} else {
85+
defaultZkHost.set(null);
86+
}
87+
}
88+
}
89+
7790
public synchronized CloudSolrClient getCloudSolrClient(String zkHost) {
7891
ensureOpen();
7992
Objects.requireNonNull(zkHost, "ZooKeeper host cannot be null!");
8093
if (solrClients.containsKey(zkHost)) {
8194
return (CloudSolrClient) solrClients.get(zkHost);
8295
}
96+
// Can only use ZK ACLs if there is a default ZK Host, and the given ZK host contains that
97+
// default.
98+
// Basically the ZK ACLs are assumed to be only used for the default ZK host,
99+
// thus we should only provide the ACLs to that Zookeeper instance.
100+
String zkHostNoChroot = zkHost.split("/")[0];
101+
boolean canUseACLs =
102+
Optional.ofNullable(defaultZkHost.get()).map(zkHostNoChroot::equals).orElse(false);
83103
final CloudSolrClient client;
84104
if (apacheHttpClient != null) {
85-
client = newCloudLegacySolrClient(zkHost, apacheHttpClient);
105+
client = newCloudLegacySolrClient(zkHost, apacheHttpClient, canUseACLs);
86106
} else {
87-
client = newCloudHttp2SolrClient(zkHost, http2SolrClient);
107+
client = newCloudHttp2SolrClient(zkHost, http2SolrClient, canUseACLs);
88108
}
89109
solrClients.put(zkHost, client);
90110
return client;
91111
}
92112

93113
@Deprecated
94-
private static CloudSolrClient newCloudLegacySolrClient(String zkHost, HttpClient httpClient) {
114+
private static CloudSolrClient newCloudLegacySolrClient(
115+
String zkHost, HttpClient httpClient, boolean canUseACLs) {
95116
final List<String> hosts = List.of(zkHost);
96117
var builder = new CloudLegacySolrClient.Builder(hosts, Optional.empty());
118+
builder.canUseZkACLs(canUseACLs);
97119
adjustTimeouts(builder, httpClient);
98120
var client = builder.build();
99-
client.connect();
121+
try {
122+
client.connect();
123+
} catch (Exception e) {
124+
IOUtils.closeQuietly(client);
125+
throw e;
126+
}
100127
return client;
101128
}
102129

103130
private static CloudHttp2SolrClient newCloudHttp2SolrClient(
104-
String zkHost, Http2SolrClient http2SolrClient) {
131+
String zkHost, Http2SolrClient http2SolrClient, boolean canUseACLs) {
105132
final List<String> hosts = List.of(zkHost);
106133
var builder = new CloudHttp2SolrClient.Builder(hosts, Optional.empty());
134+
builder.canUseZkACLs(canUseACLs);
107135
// using internal builder to ensure the internal client gets closed
108136
builder = builder.withInternalClientBuilder(newHttp2SolrClientBuilder(null, http2SolrClient));
109137
var client = builder.build();
110-
client.connect();
138+
try {
139+
client.connect();
140+
} catch (Exception e) {
141+
IOUtils.closeQuietly(client);
142+
throw e;
143+
}
111144
return client;
112145
}
113146

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. 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, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.solr.client.solrj.io;
18+
19+
import java.util.Map;
20+
import org.apache.solr.cloud.SolrCloudTestCase;
21+
import org.apache.solr.common.SolrException;
22+
import org.apache.solr.common.cloud.DigestZkACLProvider;
23+
import org.apache.solr.common.cloud.DigestZkCredentialsProvider;
24+
import org.apache.solr.common.cloud.SolrZkClient;
25+
import org.apache.solr.common.cloud.VMParamsZkCredentialsInjector;
26+
import org.junit.AfterClass;
27+
import org.junit.BeforeClass;
28+
import org.junit.Test;
29+
30+
public class SolrClientCacheTest extends SolrCloudTestCase {
31+
32+
private static final Map<String, String> sysProps =
33+
Map.of(
34+
SolrZkClient.ZK_CREDENTIALS_INJECTOR_CLASS_NAME_VM_PARAM_NAME,
35+
VMParamsZkCredentialsInjector.class.getName(),
36+
SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
37+
DigestZkCredentialsProvider.class.getName(),
38+
SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME,
39+
DigestZkACLProvider.class.getName(),
40+
VMParamsZkCredentialsInjector.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "admin-user",
41+
VMParamsZkCredentialsInjector.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "pass",
42+
VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME, "read-user",
43+
VMParamsZkCredentialsInjector.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME, "pass");
44+
45+
@BeforeClass
46+
public static void before() throws Exception {
47+
sysProps.forEach(System::setProperty);
48+
configureCluster(1)
49+
.formatZkServer(true)
50+
.addConfig("config", getFile("solrj/solr/configsets/streaming/conf").toPath())
51+
.configure();
52+
}
53+
54+
@AfterClass
55+
public static void after() {
56+
sysProps.keySet().forEach(System::clearProperty);
57+
}
58+
59+
@Test
60+
public void testZkACLsNotUsedWithDifferentZkHost() {
61+
try (SolrClientCache cache = new SolrClientCache()) {
62+
// This ZK Host is fake, thus the ZK ACLs should not be used
63+
cache.setDefaultZKHost("test:2181");
64+
expectThrows(
65+
SolrException.class, () -> cache.getCloudSolrClient(zkClient().getZkServerAddress()));
66+
}
67+
}
68+
69+
@Test
70+
public void testZkACLsUsedWithDifferentChroot() {
71+
try (SolrClientCache cache = new SolrClientCache()) {
72+
// The same ZK Host is used, so the ZK ACLs should still be applied
73+
cache.setDefaultZKHost(zkClient().getZkServerAddress() + "/random/chroot");
74+
cache.getCloudSolrClient(zkClient().getZkServerAddress());
75+
}
76+
}
77+
}

solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class ZkClientClusterStateProvider
4848
volatile ZkStateReader zkStateReader;
4949
private boolean closeZkStateReader = true;
5050
private final String zkHost;
51+
private final boolean canUseZkACLs;
5152
private int zkConnectTimeout = SolrZkClientTimeout.DEFAULT_ZK_CONNECT_TIMEOUT;
5253
private int zkClientTimeout = SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT;
5354

@@ -65,14 +66,22 @@ public ZkClientClusterStateProvider(ZkStateReader zkStateReader) {
6566
this.zkStateReader = zkStateReader;
6667
this.closeZkStateReader = false;
6768
this.zkHost = null;
69+
this.canUseZkACLs = true;
6870
}
6971

7072
public ZkClientClusterStateProvider(Collection<String> zkHosts, String chroot) {
73+
this(zkHosts, chroot, true);
74+
}
75+
76+
public ZkClientClusterStateProvider(
77+
Collection<String> zkHosts, String chroot, boolean canUseZkACLs) {
7178
zkHost = buildZkHostString(zkHosts, chroot);
79+
this.canUseZkACLs = canUseZkACLs;
7280
}
7381

7482
public ZkClientClusterStateProvider(String zkHost) {
7583
this.zkHost = zkHost;
84+
this.canUseZkACLs = true;
7685
}
7786

7887
/**
@@ -212,7 +221,7 @@ public ZkStateReader getZkStateReader() {
212221
if (zkStateReader == null) {
213222
ZkStateReader zk = null;
214223
try {
215-
zk = new ZkStateReader(zkHost, zkClientTimeout, zkConnectTimeout);
224+
zk = new ZkStateReader(zkHost, zkClientTimeout, zkConnectTimeout, canUseZkACLs);
216225
zk.createClusterStateWatchersAndUpdate();
217226
log.info("Cluster at {} ready", zkHost);
218227
zkStateReader = zk;

solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/SolrZkClient.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ public SolrZkClient(Builder builder) {
118118
builder.zkACLProvider,
119119
builder.higherLevelIsClosed,
120120
builder.compressor,
121-
builder.solrClassLoader);
121+
builder.solrClassLoader,
122+
builder.useDefaultCredsAndACLs);
122123
}
123124

124125
private SolrZkClient(
@@ -131,7 +132,8 @@ private SolrZkClient(
131132
ZkACLProvider zkACLProvider,
132133
IsClosed higherLevelIsClosed,
133134
Compressor compressor,
134-
SolrClassLoader solrClassLoader) {
135+
SolrClassLoader solrClassLoader,
136+
boolean useDefaultCredsAndACLs) {
135137

136138
if (zkServerAddress == null) {
137139
// only tests should create one without server address
@@ -148,9 +150,14 @@ private SolrZkClient(
148150

149151
this.solrClassLoader = solrClassLoader;
150152
if (!strat.hasZkCredentialsToAddAutomatically()) {
151-
zkCredentialsInjector = createZkCredentialsInjector();
153+
zkCredentialsInjector =
154+
useDefaultCredsAndACLs
155+
? createZkCredentialsInjector()
156+
: new DefaultZkCredentialsInjector();
152157
ZkCredentialsProvider zkCredentialsToAddAutomatically =
153-
createZkCredentialsToAddAutomatically();
158+
useDefaultCredsAndACLs
159+
? createZkCredentialsToAddAutomatically()
160+
: new DefaultZkCredentialsProvider();
154161
strat.setZkCredentialsToAddAutomatically(zkCredentialsToAddAutomatically);
155162
}
156163

@@ -210,7 +217,8 @@ private SolrZkClient(
210217
}
211218
assert ObjectReleaseTracker.track(this);
212219
if (zkACLProvider == null) {
213-
this.zkACLProvider = createZkACLProvider();
220+
this.zkACLProvider =
221+
useDefaultCredsAndACLs ? createZkACLProvider() : new DefaultZkACLProvider();
214222
} else {
215223
this.zkACLProvider = zkACLProvider;
216224
}
@@ -1134,6 +1142,7 @@ public static class Builder {
11341142
public ZkACLProvider zkACLProvider;
11351143
public IsClosed higherLevelIsClosed;
11361144
public SolrClassLoader solrClassLoader;
1145+
public boolean useDefaultCredsAndACLs = true;
11371146

11381147
public Compressor compressor;
11391148

@@ -1199,6 +1208,11 @@ public Builder withSolrClassLoader(SolrClassLoader solrClassLoader) {
11991208
return this;
12001209
}
12011210

1211+
public Builder withUseDefaultCredsAndACLs(boolean useDefaultCredsAndACLs) {
1212+
this.useDefaultCredsAndACLs = useDefaultCredsAndACLs;
1213+
return this;
1214+
}
1215+
12021216
public SolrZkClient build() {
12031217
return new SolrZkClient(this);
12041218
}

solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkStateReader.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,20 @@ public ZkStateReader(SolrZkClient zkClient, Runnable securityNodeListener) {
405405
}
406406

407407
public ZkStateReader(String zkServerAddress, int zkClientTimeout, int zkClientConnectTimeout) {
408-
this.zkClient =
408+
this(zkServerAddress, zkClientTimeout, zkClientConnectTimeout, true);
409+
}
410+
411+
public ZkStateReader(
412+
String zkServerAddress,
413+
int zkClientTimeout,
414+
int zkClientConnectTimeout,
415+
boolean canUseZkACLs) {
416+
SolrZkClient.Builder builder =
409417
new SolrZkClient.Builder()
410418
.withUrl(zkServerAddress)
411419
.withTimeout(zkClientTimeout, TimeUnit.MILLISECONDS)
412420
.withConnTimeOut(zkClientConnectTimeout, TimeUnit.MILLISECONDS)
421+
.withUseDefaultCredsAndACLs(canUseZkACLs)
413422
.withReconnectListener(
414423
() -> {
415424
// on reconnect, reload cloud info
@@ -425,8 +434,8 @@ public ZkStateReader(String zkServerAddress, int zkClientTimeout, int zkClientCo
425434
log.error("Interrupted", e);
426435
throw new ZooKeeperException(ErrorCode.SERVER_ERROR, "Interrupted", e);
427436
}
428-
})
429-
.build();
437+
});
438+
this.zkClient = builder.build();
430439
this.closeClient = true;
431440
this.securityNodeWatcher = null;
432441

solr/solrj/src/java/org/apache/solr/client/solrj/impl/CloudHttp2SolrClient.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public static class Builder {
138138
private int parallelCacheRefreshesLocks = 3;
139139
private int zkConnectTimeout = SolrZkClientTimeout.DEFAULT_ZK_CONNECT_TIMEOUT;
140140
private int zkClientTimeout = SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT;
141+
private boolean canUseZkACLs = true;
141142

142143
/**
143144
* Provide a series of Solr URLs to be used when configuring {@link CloudHttp2SolrClient}
@@ -189,6 +190,12 @@ public Builder(List<String> zkHosts, Optional<String> zkChroot) {
189190
if (zkChroot.isPresent()) this.zkChroot = zkChroot.get();
190191
}
191192

193+
/** Whether or not to use the default ZK ACLs when building a ZK Client. */
194+
public Builder canUseZkACLs(boolean canUseZkACLs) {
195+
this.canUseZkACLs = canUseZkACLs;
196+
return this;
197+
}
198+
192199
/**
193200
* Tells {@link Builder} that created clients should be configured such that {@link
194201
* CloudSolrClient#isUpdatesToLeaders} returns <code>true</code>.
@@ -406,7 +413,8 @@ public CloudHttp2SolrClient build() {
406413
throw new IllegalArgumentException(
407414
"Both zkHost(s) & solrUrl(s) have been specified. Only specify one.");
408415
} else if (!zkHosts.isEmpty()) {
409-
stateProvider = ClusterStateProvider.newZkClusterStateProvider(zkHosts, zkChroot);
416+
stateProvider =
417+
ClusterStateProvider.newZkClusterStateProvider(zkHosts, zkChroot, canUseZkACLs);
410418
if (stateProvider instanceof SolrZkClientTimeoutAware) {
411419
var timeoutAware = (SolrZkClientTimeoutAware) stateProvider;
412420
timeoutAware.setZkClientTimeout(zkClientTimeout);

0 commit comments

Comments
 (0)