SOLR-9520: Kerberos delegation support in SolrJ

This commit is contained in:
Noble Paul 2016-10-04 00:19:20 +05:30
parent e7ade287e3
commit 3a76ef1193
5 changed files with 138 additions and 88 deletions

View File

@ -115,6 +115,7 @@ New Features
* SOLR-9205: Added method LukeResponse.getSchemaFlags() which returns field
information as an EnumSet (Fengtan, Alan Woodward)
* SOLR-9520: Kerberos delegation support in SolrJ (Ishan Chattopadhyaya, noble)
Bug Fixes
----------------------

View File

@ -20,7 +20,10 @@ import junit.framework.Assert;
import org.apache.hadoop.util.Time;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.LBHttpSolrClient;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@ -171,30 +174,39 @@ public class TestSolrCloudWithDelegationTokens extends SolrTestCaseJ4 {
private int getStatusCode(String token, final String user, final String op, HttpSolrClient client)
throws Exception {
HttpSolrClient delegationTokenServer =
new HttpSolrClient.Builder(client.getBaseURL().toString())
.withDelegationToken(token)
SolrClient delegationTokenClient;
if (random().nextBoolean()) delegationTokenClient = new HttpSolrClient.Builder(client.getBaseURL().toString())
.withKerberosDelegationToken(token)
.withResponseParser(client.getParser())
.build();
else delegationTokenClient = new CloudSolrClient.Builder()
.withZkHost((miniCluster.getZkServer().getZkAddress()))
.withLBHttpSolrClientBuilder(new LBHttpSolrClient.Builder()
.withResponseParser(client.getParser())
.build();
.withHttpSolrClientBuilder(
new HttpSolrClient.Builder()
.withKerberosDelegationToken(token)
))
.build();
try {
ModifiableSolrParams p = new ModifiableSolrParams();
if (user != null) p.set(USER_PARAM, user);
if (op != null) p.set("op", op);
SolrRequest req = getAdminRequest(p);
if (user != null || op != null) {
Set<String> queryParams = new HashSet<String>();
Set<String> queryParams = new HashSet<>();
if (user != null) queryParams.add(USER_PARAM);
if (op != null) queryParams.add("op");
req.setQueryParams(queryParams);
}
try {
delegationTokenServer.request(req, null, null);
delegationTokenClient.request(req, null);
return HttpStatus.SC_OK;
} catch (HttpSolrClient.RemoteSolrException re) {
return re.code();
}
} finally {
delegationTokenServer.close();
delegationTokenClient.close();
}
}

View File

@ -341,7 +341,7 @@ public class CloudSolrClient extends SolrClient {
* the {@link HttpClient} instance to be used for all requests. The provided httpClient should use a
* multi-threaded connection manager. If null, a default HttpClient will be used.
* @param lbSolrClient
* LBHttpSolrServer instance for requests. If null, a default HttpClient will be used.
* LBHttpSolrClient instance for requests. If null, a default LBHttpSolrClient will be used.
* @param updatesToLeaders
* If true, sends updates to shard leaders.
*
@ -350,7 +350,7 @@ public class CloudSolrClient extends SolrClient {
*/
@Deprecated
public CloudSolrClient(Collection<String> zkHosts, String chroot, HttpClient httpClient, LBHttpSolrClient lbSolrClient, boolean updatesToLeaders) {
this(zkHosts, chroot, httpClient, lbSolrClient, updatesToLeaders, false);
this(zkHosts, chroot, httpClient, lbSolrClient, null, updatesToLeaders, false);
}
/**
@ -371,23 +371,31 @@ public class CloudSolrClient extends SolrClient {
* the {@link HttpClient} instance to be used for all requests. The provided httpClient should use a
* multi-threaded connection manager. If null, a default HttpClient will be used.
* @param lbSolrClient
* LBHttpSolrServer instance for requests. If null, a default HttpClient will be used.
* LBHttpSolrClient instance for requests. If null, a default LBHttpSolrClient will be used.
* @param lbHttpSolrClientBuilder
* LBHttpSolrClient builder to construct the LBHttpSolrClient. If null, a default builder will be used.
* @param updatesToLeaders
* If true, sends updates to shard leaders.
* @param directUpdatesToLeadersOnly
* If true, sends direct updates to shard leaders only.
*/
private CloudSolrClient(Collection<String> zkHosts, String chroot, HttpClient httpClient, LBHttpSolrClient lbSolrClient,
boolean updatesToLeaders, boolean directUpdatesToLeadersOnly) {
private CloudSolrClient(Collection<String> zkHosts,
String chroot,
HttpClient httpClient,
LBHttpSolrClient lbSolrClient,
LBHttpSolrClient.Builder lbHttpSolrClientBuilder,
boolean updatesToLeaders,
boolean directUpdatesToLeadersOnly) {
this.clientIsInternal = httpClient == null;
this.shutdownLBHttpSolrServer = lbSolrClient == null;
if(lbHttpSolrClientBuilder != null) lbSolrClient = lbHttpSolrClientBuilder.build();
if(lbSolrClient != null) httpClient = lbSolrClient.getHttpClient();
this.myClient = httpClient == null ? HttpClientUtil.createClient(null) : httpClient;
if (lbSolrClient == null) lbSolrClient = createLBHttpSolrClient(myClient);
this.lbClient = lbSolrClient;
this.zkHost = buildZkHostString(zkHosts, chroot);
this.updatesToLeaders = updatesToLeaders;
this.directUpdatesToLeadersOnly = directUpdatesToLeadersOnly;
this.clientIsInternal = httpClient == null;
this.myClient = httpClient == null ? HttpClientUtil.createClient(null) : httpClient;
this.shutdownLBHttpSolrServer = lbSolrClient == null;
this.lbClient = lbSolrClient == null ? createLBHttpSolrClient(myClient) : lbSolrClient;
}
/**
@ -475,7 +483,7 @@ public class CloudSolrClient extends SolrClient {
public ResponseParser getParser() {
return lbClient.getParser();
}
/**
* Note: This setter method is <b>not thread-safe</b>.
*
@ -1553,6 +1561,7 @@ public class CloudSolrClient extends SolrClient {
private HttpClient httpClient;
private String zkChroot;
private LBHttpSolrClient loadBalancedSolrClient;
private LBHttpSolrClient.Builder lbClientBuilder;
private boolean shardLeadersOnly;
private boolean directUpdatesToLeadersOnly;
@ -1575,6 +1584,14 @@ public class CloudSolrClient extends SolrClient {
return this;
}
/**
* Provides a {@link HttpClient} for the builder to use when creating clients.
*/
public Builder withLBHttpSolrClientBuilder(LBHttpSolrClient.Builder lbHttpSolrClientBuilder) {
this.lbClientBuilder = lbHttpSolrClientBuilder;
return this;
}
/**
* Provides a {@link HttpClient} for the builder to use when creating clients.
*/
@ -1582,7 +1599,8 @@ public class CloudSolrClient extends SolrClient {
this.httpClient = httpClient;
return this;
}
/**
* Provide a series of ZooKeeper client endpoints for the builder to use when creating clients.
*
@ -1652,7 +1670,7 @@ public class CloudSolrClient extends SolrClient {
* Create a {@link CloudSolrClient} based on the provided configuration.
*/
public CloudSolrClient build() {
return new CloudSolrClient(zkHosts, zkChroot, httpClient, loadBalancedSolrClient,
return new CloudSolrClient(zkHosts, zkChroot, httpClient, loadBalancedSolrClient, lbClientBuilder,
shardLeadersOnly, directUpdatesToLeadersOnly);
}
}

View File

@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -31,7 +30,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@ -746,43 +744,6 @@ public class HttpSolrClient extends SolrClient {
}
}
private static class DelegationTokenHttpSolrClient extends HttpSolrClient {
private final String DELEGATION_TOKEN_PARAM = "delegation";
private final String delegationToken;
public DelegationTokenHttpSolrClient(String baseURL,
HttpClient client,
ResponseParser parser,
boolean allowCompression,
String delegationToken) {
super(baseURL, client, parser, allowCompression);
if (delegationToken == null) {
throw new IllegalArgumentException("Delegation token cannot be null");
}
this.delegationToken = delegationToken;
setQueryParams(new TreeSet<String>(Arrays.asList(DELEGATION_TOKEN_PARAM)));
invariantParams = new ModifiableSolrParams();
invariantParams.set(DELEGATION_TOKEN_PARAM, delegationToken);
}
@Override
protected HttpRequestBase createMethod(final SolrRequest request, String collection) throws IOException, SolrServerException {
SolrParams params = request.getParams();
if (params.getParams(DELEGATION_TOKEN_PARAM) != null) {
throw new IllegalArgumentException(DELEGATION_TOKEN_PARAM + " parameter not supported");
}
return super.createMethod(request, collection);
}
@Override
public void setQueryParams(Set<String> queryParams) {
if (queryParams == null || !queryParams.contains(DELEGATION_TOKEN_PARAM)) {
throw new IllegalArgumentException("Query params must contain " + DELEGATION_TOKEN_PARAM);
}
super.setQueryParams(queryParams);
}
}
/**
* Constructs {@link HttpSolrClient} instances from provided configuration.
*/
@ -792,7 +753,16 @@ public class HttpSolrClient extends SolrClient {
private ResponseParser responseParser;
private boolean compression;
private String delegationToken;
public Builder() {
this.responseParser = new BinaryResponseParser();
}
public Builder withBaseSolrUrl(String baseSolrUrl) {
this.baseSolrUrl = baseSolrUrl;
return this;
}
/**
* Create a Builder object, based on the provided Solr URL.
*
@ -832,6 +802,15 @@ public class HttpSolrClient extends SolrClient {
/**
* Use a delegation token for authenticating via the KerberosPlugin
*/
public Builder withKerberosDelegationToken(String delegationToken) {
this.delegationToken = delegationToken;
return this;
}
@Deprecated
/**
* @deprecated use {@link withKerberosDelegationToken(String)} instead
*/
public Builder withDelegationToken(String delegationToken) {
this.delegationToken = delegationToken;
return this;

View File

@ -118,6 +118,7 @@ public class LBHttpSolrClient extends SolrClient {
private final HttpClient httpClient;
private final boolean clientIsInternal;
private HttpSolrClient.Builder httpSolrClientBuilder;
private final AtomicInteger counter = new AtomicInteger(-1);
private static final SolrQuery solrQuery = new SolrQuery("*:*");
@ -242,6 +243,26 @@ public class LBHttpSolrClient extends SolrClient {
this(httpClient, new BinaryResponseParser(), solrServerUrl);
}
/**
* The provided httpClient should use a multi-threaded connection manager
* @deprecated use {@link Builder} instead. This will soon be a protected
* method and will only be available for use in implementing subclasses.
*/
public LBHttpSolrClient(HttpSolrClient.Builder httpSolrClientBuilder,
HttpClient httpClient, String... solrServerUrl) {
clientIsInternal = httpClient == null;
this.httpSolrClientBuilder = httpSolrClientBuilder;
httpClient = constructClient(null);
this.httpClient = httpClient;
if (solrServerUrl != null) {
for (String s : solrServerUrl) {
ServerWrapper wrapper = new ServerWrapper(makeSolrClient(s));
aliveServers.put(wrapper.getKey(), wrapper);
}
}
updateAliveList();
}
/**
* The provided httpClient should use a multi-threaded connection manager
* @deprecated use {@link Builder} instead. This will soon be a protected
@ -250,19 +271,7 @@ public class LBHttpSolrClient extends SolrClient {
@Deprecated
public LBHttpSolrClient(HttpClient httpClient, ResponseParser parser, String... solrServerUrl) {
clientIsInternal = (httpClient == null);
if (httpClient == null) {
ModifiableSolrParams params = new ModifiableSolrParams();
if (solrServerUrl.length > 1) {
// we prefer retrying another server
params.set(HttpClientUtil.PROP_USE_RETRY, false);
} else {
params.set(HttpClientUtil.PROP_USE_RETRY, true);
}
this.httpClient = HttpClientUtil.createClient(params);
} else {
this.httpClient = httpClient;
}
this.httpClient = httpClient == null ? constructClient(solrServerUrl) : httpClient;
this.parser = parser;
for (String s : solrServerUrl) {
@ -271,7 +280,18 @@ public class LBHttpSolrClient extends SolrClient {
}
updateAliveList();
}
private HttpClient constructClient(String[] solrServerUrl) {
ModifiableSolrParams params = new ModifiableSolrParams();
if (solrServerUrl != null && solrServerUrl.length > 1) {
// we prefer retrying another server
params.set(HttpClientUtil.PROP_USE_RETRY, false);
} else {
params.set(HttpClientUtil.PROP_USE_RETRY, true);
}
return HttpClientUtil.createClient(params);
}
public Set<String> getQueryParams() {
return queryParams;
}
@ -294,15 +314,19 @@ public class LBHttpSolrClient extends SolrClient {
}
protected HttpSolrClient makeSolrClient(String server) {
HttpSolrClient client = new HttpSolrClient.Builder(server)
.withHttpClient(httpClient)
.withResponseParser(parser)
.build();
if (connectionTimeout != null) {
client.setConnectionTimeout(connectionTimeout);
}
if (soTimeout != null) {
client.setSoTimeout(soTimeout);
HttpSolrClient client;
if (httpSolrClientBuilder != null) {
synchronized (this) {
client = httpSolrClientBuilder
.withBaseSolrUrl(server)
.withHttpClient(httpClient)
.build();
}
} else {
client = new HttpSolrClient.Builder(server)
.withHttpClient(httpClient)
.withResponseParser(parser)
.build();
}
if (requestWriter != null) {
client.setRequestWriter(requestWriter);
@ -787,11 +811,16 @@ public class LBHttpSolrClient extends SolrClient {
private final List<String> baseSolrUrls;
private HttpClient httpClient;
private ResponseParser responseParser;
private HttpSolrClient.Builder httpSolrClientBuilder;
public Builder() {
this.baseSolrUrls = new ArrayList<String>();
this.baseSolrUrls = new ArrayList<>();
this.responseParser = new BinaryResponseParser();
}
public HttpSolrClient.Builder getHttpSolrClientBuilder() {
return httpSolrClientBuilder;
}
/**
* Provide a Solr endpoint to be used when configuring {@link LBHttpSolrClient} instances.
@ -831,13 +860,24 @@ public class LBHttpSolrClient extends SolrClient {
this.responseParser = responseParser;
return this;
}
/**
* Provides a {@link HttpSolrClient.Builder} to be used for building the internally used clients.
*/
public Builder withHttpSolrClientBuilder(HttpSolrClient.Builder builder) {
this.httpSolrClientBuilder = builder;
return this;
}
/**
* Create a {@link HttpSolrClient} based on provided configuration.
*/
public LBHttpSolrClient build() {
final String[] baseUrlArray = new String[baseSolrUrls.size()];
return new LBHttpSolrClient(httpClient, responseParser, baseSolrUrls.toArray(baseUrlArray));
String[] solrServerUrls = baseSolrUrls.toArray(baseUrlArray);
return httpSolrClientBuilder != null ?
new LBHttpSolrClient(httpSolrClientBuilder, httpClient, solrServerUrls) :
new LBHttpSolrClient(httpClient, responseParser, solrServerUrls);
}
}
}