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 * SOLR-9205: Added method LukeResponse.getSchemaFlags() which returns field
information as an EnumSet (Fengtan, Alan Woodward) information as an EnumSet (Fengtan, Alan Woodward)
* SOLR-9520: Kerberos delegation support in SolrJ (Ishan Chattopadhyaya, noble)
Bug Fixes Bug Fixes
---------------------- ----------------------

View File

@ -20,7 +20,10 @@ import junit.framework.Assert;
import org.apache.hadoop.util.Time; import org.apache.hadoop.util.Time;
import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.SolrTestCaseJ4; 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.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.SolrRequest;
import org.apache.solr.client.solrj.embedded.JettySolrRunner; import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.request.CollectionAdminRequest; 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) private int getStatusCode(String token, final String user, final String op, HttpSolrClient client)
throws Exception { throws Exception {
HttpSolrClient delegationTokenServer = SolrClient delegationTokenClient;
new HttpSolrClient.Builder(client.getBaseURL().toString()) if (random().nextBoolean()) delegationTokenClient = new HttpSolrClient.Builder(client.getBaseURL().toString())
.withDelegationToken(token) .withKerberosDelegationToken(token)
.withResponseParser(client.getParser())
.build();
else delegationTokenClient = new CloudSolrClient.Builder()
.withZkHost((miniCluster.getZkServer().getZkAddress()))
.withLBHttpSolrClientBuilder(new LBHttpSolrClient.Builder()
.withResponseParser(client.getParser()) .withResponseParser(client.getParser())
.build(); .withHttpSolrClientBuilder(
new HttpSolrClient.Builder()
.withKerberosDelegationToken(token)
))
.build();
try { try {
ModifiableSolrParams p = new ModifiableSolrParams(); ModifiableSolrParams p = new ModifiableSolrParams();
if (user != null) p.set(USER_PARAM, user); if (user != null) p.set(USER_PARAM, user);
if (op != null) p.set("op", op); if (op != null) p.set("op", op);
SolrRequest req = getAdminRequest(p); SolrRequest req = getAdminRequest(p);
if (user != null || op != null) { if (user != null || op != null) {
Set<String> queryParams = new HashSet<String>(); Set<String> queryParams = new HashSet<>();
if (user != null) queryParams.add(USER_PARAM); if (user != null) queryParams.add(USER_PARAM);
if (op != null) queryParams.add("op"); if (op != null) queryParams.add("op");
req.setQueryParams(queryParams); req.setQueryParams(queryParams);
} }
try { try {
delegationTokenServer.request(req, null, null); delegationTokenClient.request(req, null);
return HttpStatus.SC_OK; return HttpStatus.SC_OK;
} catch (HttpSolrClient.RemoteSolrException re) { } catch (HttpSolrClient.RemoteSolrException re) {
return re.code(); return re.code();
} }
} finally { } 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 * 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. * multi-threaded connection manager. If null, a default HttpClient will be used.
* @param lbSolrClient * @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 * @param updatesToLeaders
* If true, sends updates to shard leaders. * If true, sends updates to shard leaders.
* *
@ -350,7 +350,7 @@ public class CloudSolrClient extends SolrClient {
*/ */
@Deprecated @Deprecated
public CloudSolrClient(Collection<String> zkHosts, String chroot, HttpClient httpClient, LBHttpSolrClient lbSolrClient, boolean updatesToLeaders) { 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 * 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. * multi-threaded connection manager. If null, a default HttpClient will be used.
* @param lbSolrClient * @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 * @param updatesToLeaders
* If true, sends updates to shard leaders. * If true, sends updates to shard leaders.
* @param directUpdatesToLeadersOnly * @param directUpdatesToLeadersOnly
* If true, sends direct updates to shard leaders only. * If true, sends direct updates to shard leaders only.
*/ */
private CloudSolrClient(Collection<String> zkHosts, String chroot, HttpClient httpClient, LBHttpSolrClient lbSolrClient, private CloudSolrClient(Collection<String> zkHosts,
boolean updatesToLeaders, boolean directUpdatesToLeadersOnly) { 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.zkHost = buildZkHostString(zkHosts, chroot);
this.updatesToLeaders = updatesToLeaders; this.updatesToLeaders = updatesToLeaders;
this.directUpdatesToLeadersOnly = directUpdatesToLeadersOnly; 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() { public ResponseParser getParser() {
return lbClient.getParser(); return lbClient.getParser();
} }
/** /**
* Note: This setter method is <b>not thread-safe</b>. * Note: This setter method is <b>not thread-safe</b>.
* *
@ -1553,6 +1561,7 @@ public class CloudSolrClient extends SolrClient {
private HttpClient httpClient; private HttpClient httpClient;
private String zkChroot; private String zkChroot;
private LBHttpSolrClient loadBalancedSolrClient; private LBHttpSolrClient loadBalancedSolrClient;
private LBHttpSolrClient.Builder lbClientBuilder;
private boolean shardLeadersOnly; private boolean shardLeadersOnly;
private boolean directUpdatesToLeadersOnly; private boolean directUpdatesToLeadersOnly;
@ -1575,6 +1584,14 @@ public class CloudSolrClient extends SolrClient {
return this; 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. * Provides a {@link HttpClient} for the builder to use when creating clients.
*/ */
@ -1582,7 +1599,8 @@ public class CloudSolrClient extends SolrClient {
this.httpClient = httpClient; this.httpClient = httpClient;
return this; return this;
} }
/** /**
* Provide a series of ZooKeeper client endpoints for the builder to use when creating clients. * 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. * Create a {@link CloudSolrClient} based on the provided configuration.
*/ */
public CloudSolrClient build() { public CloudSolrClient build() {
return new CloudSolrClient(zkHosts, zkChroot, httpClient, loadBalancedSolrClient, return new CloudSolrClient(zkHosts, zkChroot, httpClient, loadBalancedSolrClient, lbClientBuilder,
shardLeadersOnly, directUpdatesToLeadersOnly); shardLeadersOnly, directUpdatesToLeadersOnly);
} }
} }

View File

@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -31,7 +30,6 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; 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. * Constructs {@link HttpSolrClient} instances from provided configuration.
*/ */
@ -792,7 +753,16 @@ public class HttpSolrClient extends SolrClient {
private ResponseParser responseParser; private ResponseParser responseParser;
private boolean compression; private boolean compression;
private String delegationToken; 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. * 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 * 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) { public Builder withDelegationToken(String delegationToken) {
this.delegationToken = delegationToken; this.delegationToken = delegationToken;
return this; return this;

View File

@ -118,6 +118,7 @@ public class LBHttpSolrClient extends SolrClient {
private final HttpClient httpClient; private final HttpClient httpClient;
private final boolean clientIsInternal; private final boolean clientIsInternal;
private HttpSolrClient.Builder httpSolrClientBuilder;
private final AtomicInteger counter = new AtomicInteger(-1); private final AtomicInteger counter = new AtomicInteger(-1);
private static final SolrQuery solrQuery = new SolrQuery("*:*"); private static final SolrQuery solrQuery = new SolrQuery("*:*");
@ -242,6 +243,26 @@ public class LBHttpSolrClient extends SolrClient {
this(httpClient, new BinaryResponseParser(), solrServerUrl); 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 * The provided httpClient should use a multi-threaded connection manager
* @deprecated use {@link Builder} instead. This will soon be a protected * @deprecated use {@link Builder} instead. This will soon be a protected
@ -250,19 +271,7 @@ public class LBHttpSolrClient extends SolrClient {
@Deprecated @Deprecated
public LBHttpSolrClient(HttpClient httpClient, ResponseParser parser, String... solrServerUrl) { public LBHttpSolrClient(HttpClient httpClient, ResponseParser parser, String... solrServerUrl) {
clientIsInternal = (httpClient == null); clientIsInternal = (httpClient == null);
if (httpClient == null) { this.httpClient = httpClient == null ? constructClient(solrServerUrl) : httpClient;
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.parser = parser; this.parser = parser;
for (String s : solrServerUrl) { for (String s : solrServerUrl) {
@ -271,7 +280,18 @@ public class LBHttpSolrClient extends SolrClient {
} }
updateAliveList(); 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() { public Set<String> getQueryParams() {
return queryParams; return queryParams;
} }
@ -294,15 +314,19 @@ public class LBHttpSolrClient extends SolrClient {
} }
protected HttpSolrClient makeSolrClient(String server) { protected HttpSolrClient makeSolrClient(String server) {
HttpSolrClient client = new HttpSolrClient.Builder(server) HttpSolrClient client;
.withHttpClient(httpClient) if (httpSolrClientBuilder != null) {
.withResponseParser(parser) synchronized (this) {
.build(); client = httpSolrClientBuilder
if (connectionTimeout != null) { .withBaseSolrUrl(server)
client.setConnectionTimeout(connectionTimeout); .withHttpClient(httpClient)
} .build();
if (soTimeout != null) { }
client.setSoTimeout(soTimeout); } else {
client = new HttpSolrClient.Builder(server)
.withHttpClient(httpClient)
.withResponseParser(parser)
.build();
} }
if (requestWriter != null) { if (requestWriter != null) {
client.setRequestWriter(requestWriter); client.setRequestWriter(requestWriter);
@ -787,11 +811,16 @@ public class LBHttpSolrClient extends SolrClient {
private final List<String> baseSolrUrls; private final List<String> baseSolrUrls;
private HttpClient httpClient; private HttpClient httpClient;
private ResponseParser responseParser; private ResponseParser responseParser;
private HttpSolrClient.Builder httpSolrClientBuilder;
public Builder() { public Builder() {
this.baseSolrUrls = new ArrayList<String>(); this.baseSolrUrls = new ArrayList<>();
this.responseParser = new BinaryResponseParser(); this.responseParser = new BinaryResponseParser();
} }
public HttpSolrClient.Builder getHttpSolrClientBuilder() {
return httpSolrClientBuilder;
}
/** /**
* Provide a Solr endpoint to be used when configuring {@link LBHttpSolrClient} instances. * Provide a Solr endpoint to be used when configuring {@link LBHttpSolrClient} instances.
@ -831,13 +860,24 @@ public class LBHttpSolrClient extends SolrClient {
this.responseParser = responseParser; this.responseParser = responseParser;
return this; 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. * Create a {@link HttpSolrClient} based on provided configuration.
*/ */
public LBHttpSolrClient build() { public LBHttpSolrClient build() {
final String[] baseUrlArray = new String[baseSolrUrls.size()]; 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);
} }
} }
} }