mirror of https://github.com/apache/lucene.git
SOLR-13407: Reject update requests sent to non-routed multi collection aliases.
This commit is contained in:
parent
61d7569f78
commit
f46ba5227e
|
@ -94,6 +94,10 @@ Upgrade Notes
|
||||||
Please see https://velocity.apache.org/engine/2.0/upgrading.html about upgrading. Velocity Tools upgraded from 2.0 to
|
Please see https://velocity.apache.org/engine/2.0/upgrading.html about upgrading. Velocity Tools upgraded from 2.0 to
|
||||||
3.0. For more details, please see https://velocity.apache.org/tools/3.0/upgrading.html for details about the upgrade.
|
3.0. For more details, please see https://velocity.apache.org/tools/3.0/upgrading.html for details about the upgrade.
|
||||||
|
|
||||||
|
* SOLR-13407: Update requests sent to non-routed aliases that point to multiple collections are no longer accepted. Until
|
||||||
|
now Solr followed an obscure convention of updating only the first collection from the list, which usually was not what
|
||||||
|
the user intended. This change explicitly rejects such update requests.
|
||||||
|
|
||||||
|
|
||||||
New Features
|
New Features
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -226,6 +230,8 @@ Improvements
|
||||||
|
|
||||||
* SOLR-13398: Move log line "Processing SSL Credential Provider chain..." from INFO to DEBUG (janhoy)
|
* SOLR-13398: Move log line "Processing SSL Credential Provider chain..." from INFO to DEBUG (janhoy)
|
||||||
|
|
||||||
|
* SOLR-13407: Reject update requests sent to non-routed multi collection aliases. (ab)
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,11 @@ public class V2HttpCall extends HttpSolrCall {
|
||||||
|
|
||||||
Supplier<DocCollection> logic = () -> {
|
Supplier<DocCollection> logic = () -> {
|
||||||
this.collectionsList = resolveCollectionListOrAlias(collectionStr); // side-effect
|
this.collectionsList = resolveCollectionListOrAlias(collectionStr); // side-effect
|
||||||
|
if (collectionsList.size() > 1) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Request must be sent to a single collection " +
|
||||||
|
"or an alias that points to a single collection," +
|
||||||
|
" but '" + collectionStr + "' resolves to " + this.collectionsList);
|
||||||
|
}
|
||||||
String collectionName = collectionsList.get(0); // first
|
String collectionName = collectionsList.get(0); // first
|
||||||
//TODO an option to choose another collection in the list if can't find a local replica of the first?
|
//TODO an option to choose another collection in the list if can't find a local replica of the first?
|
||||||
|
|
||||||
|
|
|
@ -371,17 +371,29 @@ public class HttpSolrCall {
|
||||||
* {@link #getCollectionsList()}
|
* {@link #getCollectionsList()}
|
||||||
*/
|
*/
|
||||||
protected List<String> resolveCollectionListOrAlias(String collectionStr) {
|
protected List<String> resolveCollectionListOrAlias(String collectionStr) {
|
||||||
if (collectionStr == null) {
|
if (collectionStr == null || collectionStr.trim().isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
LinkedHashSet<String> resultList = new LinkedHashSet<>();
|
List<String> result = null;
|
||||||
|
LinkedHashSet<String> uniqueList = null;
|
||||||
Aliases aliases = getAliases();
|
Aliases aliases = getAliases();
|
||||||
List<String> inputCollections = StrUtils.splitSmart(collectionStr, ",", true);
|
List<String> inputCollections = StrUtils.splitSmart(collectionStr, ",", true);
|
||||||
|
if (inputCollections.size() > 1) {
|
||||||
|
uniqueList = new LinkedHashSet<>();
|
||||||
|
}
|
||||||
for (String inputCollection : inputCollections) {
|
for (String inputCollection : inputCollections) {
|
||||||
List<String> resolvedCollections = aliases.resolveAliases(inputCollection);
|
List<String> resolvedCollections = aliases.resolveAliases(inputCollection);
|
||||||
resultList.addAll(resolvedCollections);
|
if (uniqueList != null) {
|
||||||
|
uniqueList.addAll(resolvedCollections);
|
||||||
|
} else {
|
||||||
|
result = resolvedCollections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniqueList != null) {
|
||||||
|
return new ArrayList<>(uniqueList);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return new ArrayList<>(resultList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -430,7 +442,7 @@ public class HttpSolrCall {
|
||||||
|
|
||||||
protected void extractRemotePath(String collectionName, String origCorename) throws UnsupportedEncodingException, KeeperException, InterruptedException {
|
protected void extractRemotePath(String collectionName, String origCorename) throws UnsupportedEncodingException, KeeperException, InterruptedException {
|
||||||
assert core == null;
|
assert core == null;
|
||||||
coreUrl = getRemotCoreUrl(collectionName, origCorename);
|
coreUrl = getRemoteCoreUrl(collectionName, origCorename);
|
||||||
// don't proxy for internal update requests
|
// don't proxy for internal update requests
|
||||||
invalidStates = checkStateVersionsAreValid(queryParams.get(CloudSolrClient.STATE_VERSION));
|
invalidStates = checkStateVersionsAreValid(queryParams.get(CloudSolrClient.STATE_VERSION));
|
||||||
if (coreUrl != null
|
if (coreUrl != null
|
||||||
|
@ -927,7 +939,7 @@ public class HttpSolrCall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getRemotCoreUrl(String collectionName, String origCorename) {
|
protected String getRemoteCoreUrl(String collectionName, String origCorename) {
|
||||||
ClusterState clusterState = cores.getZkController().getClusterState();
|
ClusterState clusterState = cores.getZkController().getClusterState();
|
||||||
final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName);
|
final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName);
|
||||||
Slice[] slices = (docCollection != null) ? docCollection.getActiveSlicesArr() : null;
|
Slice[] slices = (docCollection != null) ? docCollection.getActiveSlicesArr() : null;
|
||||||
|
@ -951,7 +963,12 @@ public class HttpSolrCall {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionsList.add(collectionName);
|
// XXX (ab) most likely this is not needed? it seems all code paths
|
||||||
|
// XXX already make sure the collectionName is on the list
|
||||||
|
if (!collectionsList.contains(collectionName)) {
|
||||||
|
collectionsList = new ArrayList<>(collectionsList);
|
||||||
|
collectionsList.add(collectionName);
|
||||||
|
}
|
||||||
String coreUrl = getCoreUrl(collectionName, origCorename, clusterState,
|
String coreUrl = getCoreUrl(collectionName, origCorename, clusterState,
|
||||||
activeSlices, byCoreName, true);
|
activeSlices, byCoreName, true);
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,10 @@ import org.apache.lucene.util.IOUtils;
|
||||||
import org.apache.solr.client.solrj.SolrQuery;
|
import org.apache.solr.client.solrj.SolrQuery;
|
||||||
import org.apache.solr.client.solrj.SolrRequest;
|
import org.apache.solr.client.solrj.SolrRequest;
|
||||||
import org.apache.solr.client.solrj.SolrServerException;
|
import org.apache.solr.client.solrj.SolrServerException;
|
||||||
|
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
||||||
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||||
import org.apache.solr.client.solrj.impl.CloudSolrClient;
|
import org.apache.solr.client.solrj.impl.CloudSolrClient;
|
||||||
|
import org.apache.solr.client.solrj.impl.ClusterStateProvider;
|
||||||
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
import org.apache.solr.client.solrj.impl.HttpSolrClient;
|
||||||
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
||||||
import org.apache.solr.client.solrj.request.UpdateRequest;
|
import org.apache.solr.client.solrj.request.UpdateRequest;
|
||||||
|
@ -46,6 +48,7 @@ import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.cloud.Aliases;
|
import org.apache.solr.common.cloud.Aliases;
|
||||||
import org.apache.solr.common.cloud.SolrZkClient;
|
import org.apache.solr.common.cloud.SolrZkClient;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader;
|
import org.apache.solr.common.cloud.ZkStateReader;
|
||||||
|
import org.apache.solr.common.params.CollectionAdminParams;
|
||||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||||
import org.apache.solr.common.params.SolrParams;
|
import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.common.util.Utils;
|
import org.apache.solr.common.util.Utils;
|
||||||
|
@ -269,6 +272,43 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClusterStateProviderAPI() throws Exception {
|
||||||
|
final String aliasName = getSaferTestName();
|
||||||
|
ZkStateReader zkStateReader = createColectionsAndAlias(aliasName);
|
||||||
|
CollectionAdminRequest.SetAliasProperty setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
|
||||||
|
setAliasProperty.addProperty("foo","baz");
|
||||||
|
setAliasProperty.addProperty("bar","bam");
|
||||||
|
setAliasProperty.process(cluster.getSolrClient());
|
||||||
|
checkFooAndBarMeta(aliasName, zkStateReader);
|
||||||
|
SolrCloudManager cloudManager = cluster.getJettySolrRunner(0).getCoreContainer().getZkController().getSolrCloudManager();
|
||||||
|
ClusterStateProvider stateProvider = cloudManager.getClusterStateProvider();
|
||||||
|
List<String> collections = stateProvider.resolveAlias(aliasName);
|
||||||
|
assertEquals(collections.toString(), 2, collections.size());
|
||||||
|
assertTrue(collections.toString(), collections.contains("collection1meta"));
|
||||||
|
assertTrue(collections.toString(), collections.contains("collection2meta"));
|
||||||
|
Map<String, String> props = stateProvider.getAliasProperties(aliasName);
|
||||||
|
assertEquals(props.toString(), 2, props.size());
|
||||||
|
assertEquals(props.toString(), "baz", props.get("foo"));
|
||||||
|
assertEquals(props.toString(), "bam", props.get("bar"));
|
||||||
|
|
||||||
|
assertFalse("should not be a routed alias", stateProvider.isRoutedAlias(aliasName));
|
||||||
|
// now make it a routed alias, according to the criteria in the API
|
||||||
|
setAliasProperty = CollectionAdminRequest.setAliasProperty(aliasName);
|
||||||
|
setAliasProperty.addProperty(CollectionAdminParams.ROUTER_PREFIX + "foo","baz");
|
||||||
|
setAliasProperty.process(cluster.getSolrClient());
|
||||||
|
// refresh
|
||||||
|
stateProvider = cloudManager.getClusterStateProvider();
|
||||||
|
assertTrue("should be a routed alias", stateProvider.isRoutedAlias(aliasName));
|
||||||
|
|
||||||
|
try {
|
||||||
|
String resolved = stateProvider.resolveSimpleAlias(aliasName);
|
||||||
|
fail("this is not a simple alias but it resolved to " + resolved);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkFooAndBarMeta(String aliasName, ZkStateReader zkStateReader) throws Exception {
|
private void checkFooAndBarMeta(String aliasName, ZkStateReader zkStateReader) throws Exception {
|
||||||
zkStateReader.aliasesManager.update(); // ensure our view is up to date
|
zkStateReader.aliasesManager.update(); // ensure our view is up to date
|
||||||
Map<String, String> meta = zkStateReader.getAliases().getCollectionAliasProperties(aliasName);
|
Map<String, String> meta = zkStateReader.getAliases().getCollectionAliasProperties(aliasName);
|
||||||
|
@ -555,11 +595,16 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
|
||||||
|
|
||||||
searchSeveralWays("testalias6", new SolrQuery("*:*"), 6);
|
searchSeveralWays("testalias6", new SolrQuery("*:*"), 6);
|
||||||
|
|
||||||
// add one document to testalias6, which will route to collection2 because it's the first
|
// add one document to testalias6. this should fail because it's a multi-collection non-routed alias
|
||||||
new UpdateRequest()
|
try {
|
||||||
.add("id", "12", "a_t", "humpty dumpy5 sat on a walls")
|
new UpdateRequest()
|
||||||
.commit(cluster.getSolrClient(), "testalias6"); // thus gets added to collection2
|
.add("id", "12", "a_t", "humpty dumpy5 sat on a walls")
|
||||||
searchSeveralWays("collection2", new SolrQuery("*:*"), 4);
|
.commit(cluster.getSolrClient(), "testalias6");
|
||||||
|
fail("Update to a multi-collection non-routed alias should fail");
|
||||||
|
} catch (SolrException e) {
|
||||||
|
// expected
|
||||||
|
assertEquals(e.toString(), SolrException.ErrorCode.BAD_REQUEST.code, e.code());
|
||||||
|
}
|
||||||
|
|
||||||
///////////////
|
///////////////
|
||||||
for (int i = 1; i <= 6 ; i++) {
|
for (int i = 1; i <= 6 ; i++) {
|
||||||
|
|
|
@ -2145,6 +2145,11 @@ public class SimClusterStateProvider implements ClusterStateProvider {
|
||||||
throw new UnsupportedOperationException("resolveAlias not implemented");
|
throw new UnsupportedOperationException("resolveAlias not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAliasProperties(String alias) {
|
||||||
|
throw new UnsupportedOperationException("getAliasProperties not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClusterState getClusterState() throws IOException {
|
public ClusterState getClusterState() throws IOException {
|
||||||
ensureNotClosed();
|
ensureNotClosed();
|
||||||
|
|
|
@ -25,10 +25,9 @@ alternative names for collections are known as aliases, and are useful when you
|
||||||
. Insulate the client programming versus changes in collection names
|
. Insulate the client programming versus changes in collection names
|
||||||
. Issue a single query against several collections with identical schemas
|
. Issue a single query against several collections with identical schemas
|
||||||
|
|
||||||
It's also possible to send update commands to aliases, but this is rarely useful if the
|
It's also possible to send update commands to aliases, but only to those that either resolve to a single collection
|
||||||
alias refers to more than one collection (as in case 3 above).
|
or those that define the routing between multiple collections (Routed Aliases). In other cases update commands are
|
||||||
Since there is no logic by which to distribute documents among the collections, all updates will simply be
|
rejected with an error since there is no logic by which to distribute documents among the multiple collections.
|
||||||
directed to the first collection in the list.
|
|
||||||
|
|
||||||
Standard aliases are created and updated using the <<collections-api.adoc#createalias,CREATEALIAS>> command.
|
Standard aliases are created and updated using the <<collections-api.adoc#createalias,CREATEALIAS>> command.
|
||||||
The current list of collections that are members of an alias can be verified via the
|
The current list of collections that are members of an alias can be verified via the
|
||||||
|
@ -56,7 +55,7 @@ collection name is expected. This works only when the following criteria are sat
|
||||||
* an alias must not refer to a Routed Alias (see below)
|
* an alias must not refer to a Routed Alias (see below)
|
||||||
|
|
||||||
If all criteria are satisfied then the command will resolve alias names and operate on the collections the aliases
|
If all criteria are satisfied then the command will resolve alias names and operate on the collections the aliases
|
||||||
refer to, as if it was invoked with the collection names instead. Otherwise the command will not be executed and
|
refer to as if it was invoked with the collection names instead. Otherwise the command will not be executed and
|
||||||
an exception will be thrown.
|
an exception will be thrown.
|
||||||
|
|
||||||
== Routed Aliases
|
== Routed Aliases
|
||||||
|
|
|
@ -63,6 +63,15 @@ public class DelegatingClusterStateProvider implements ClusterStateProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAliasProperties(String alias) {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.getAliasProperties(alias);
|
||||||
|
} else {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
|
public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
|
||||||
if (delegate != null) {
|
if (delegate != null) {
|
||||||
|
|
|
@ -413,9 +413,15 @@ public abstract class BaseCloudSolrClient extends SolrClient {
|
||||||
throw new SolrServerException("No collection param specified on request and no default collection has been set.");
|
throw new SolrServerException("No collection param specified on request and no default collection has been set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check to see if the collection is an alias.
|
//Check to see if the collection is an alias. Updates to multi-collection aliases are ok as long
|
||||||
|
// as they are routed aliases
|
||||||
List<String> aliasedCollections = getClusterStateProvider().resolveAlias(collection);
|
List<String> aliasedCollections = getClusterStateProvider().resolveAlias(collection);
|
||||||
collection = aliasedCollections.get(0); // pick 1st (consistent with HttpSolrCall behavior)
|
if (getClusterStateProvider().isRoutedAlias(collection) || aliasedCollections.size() == 1) {
|
||||||
|
collection = aliasedCollections.get(0); // pick 1st (consistent with HttpSolrCall behavior)
|
||||||
|
} else {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Update request to non-routed multi-collection alias not supported: "
|
||||||
|
+ collection + " -> " + aliasedCollections);
|
||||||
|
}
|
||||||
|
|
||||||
DocCollection col = getDocCollection(collection, null);
|
DocCollection col = getDocCollection(collection, null);
|
||||||
|
|
||||||
|
@ -786,8 +792,9 @@ public abstract class BaseCloudSolrClient extends SolrClient {
|
||||||
isCollectionRequestOfV2 = ((V2Request) request).isPerCollectionRequest();
|
isCollectionRequestOfV2 = ((V2Request) request).isPerCollectionRequest();
|
||||||
}
|
}
|
||||||
boolean isAdmin = ADMIN_PATHS.contains(request.getPath());
|
boolean isAdmin = ADMIN_PATHS.contains(request.getPath());
|
||||||
|
boolean isUpdate = (request instanceof IsUpdateRequest) && (request instanceof UpdateRequest);
|
||||||
if (!inputCollections.isEmpty() && !isAdmin && !isCollectionRequestOfV2) { // don't do _stateVer_ checking for admin, v2 api requests
|
if (!inputCollections.isEmpty() && !isAdmin && !isCollectionRequestOfV2) { // don't do _stateVer_ checking for admin, v2 api requests
|
||||||
Set<String> requestedCollectionNames = resolveAliases(inputCollections);
|
Set<String> requestedCollectionNames = resolveAliases(inputCollections, isUpdate);
|
||||||
|
|
||||||
StringBuilder stateVerParamBuilder = null;
|
StringBuilder stateVerParamBuilder = null;
|
||||||
for (String requestedCollection : requestedCollectionNames) {
|
for (String requestedCollection : requestedCollectionNames) {
|
||||||
|
@ -957,9 +964,15 @@ public abstract class BaseCloudSolrClient extends SolrClient {
|
||||||
connect();
|
connect();
|
||||||
|
|
||||||
boolean sendToLeaders = false;
|
boolean sendToLeaders = false;
|
||||||
|
boolean isUpdate = false;
|
||||||
|
|
||||||
if (request instanceof IsUpdateRequest) {
|
if (request instanceof IsUpdateRequest) {
|
||||||
if (request instanceof UpdateRequest) {
|
if (request instanceof UpdateRequest) {
|
||||||
|
isUpdate = true;
|
||||||
|
if (inputCollections.size() > 1) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Update request must be sent to a single collection " +
|
||||||
|
"or an alias: " + inputCollections);
|
||||||
|
}
|
||||||
String collection = inputCollections.isEmpty() ? null : inputCollections.get(0); // getting first mimics HttpSolrCall
|
String collection = inputCollections.isEmpty() ? null : inputCollections.get(0); // getting first mimics HttpSolrCall
|
||||||
NamedList<Object> response = directUpdate((AbstractUpdateRequest) request, collection);
|
NamedList<Object> response = directUpdate((AbstractUpdateRequest) request, collection);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
@ -993,7 +1006,7 @@ public abstract class BaseCloudSolrClient extends SolrClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // Typical...
|
} else { // Typical...
|
||||||
Set<String> collectionNames = resolveAliases(inputCollections);
|
Set<String> collectionNames = resolveAliases(inputCollections, isUpdate);
|
||||||
if (collectionNames.isEmpty()) {
|
if (collectionNames.isEmpty()) {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
"No collection param specified on request and no default collection has been set: " + inputCollections);
|
"No collection param specified on request and no default collection has been set: " + inputCollections);
|
||||||
|
@ -1057,17 +1070,20 @@ public abstract class BaseCloudSolrClient extends SolrClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Resolves the input collections to their possible aliased collections. Doesn't validate collection existence. */
|
/** Resolves the input collections to their possible aliased collections. Doesn't validate collection existence. */
|
||||||
private LinkedHashSet<String> resolveAliases(List<String> inputCollections) {
|
private Set<String> resolveAliases(List<String> inputCollections, boolean isUpdate) {
|
||||||
LinkedHashSet<String> collectionNames = new LinkedHashSet<>(); // consistent ordering
|
if (inputCollections.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
LinkedHashSet<String> uniqueNames = new LinkedHashSet<>(); // consistent ordering
|
||||||
for (String collectionName : inputCollections) {
|
for (String collectionName : inputCollections) {
|
||||||
if (getClusterStateProvider().getState(collectionName) == null) {
|
if (getClusterStateProvider().getState(collectionName) == null) {
|
||||||
// perhaps it's an alias
|
// perhaps it's an alias
|
||||||
collectionNames.addAll(getClusterStateProvider().resolveAlias(collectionName));
|
uniqueNames.addAll(getClusterStateProvider().resolveAlias(collectionName));
|
||||||
} else {
|
} else {
|
||||||
collectionNames.add(collectionName); // it's a collection
|
uniqueNames.add(collectionName); // it's a collection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collectionNames;
|
return uniqueNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdatesToLeaders() {
|
public boolean isUpdatesToLeaders() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.solr.client.solrj.SolrClient;
|
||||||
import org.apache.solr.client.solrj.SolrServerException;
|
import org.apache.solr.client.solrj.SolrServerException;
|
||||||
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
||||||
import org.apache.solr.client.solrj.request.QueryRequest;
|
import org.apache.solr.client.solrj.request.QueryRequest;
|
||||||
|
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
|
||||||
import org.apache.solr.common.cloud.Aliases;
|
import org.apache.solr.common.cloud.Aliases;
|
||||||
import org.apache.solr.common.cloud.ClusterState;
|
import org.apache.solr.common.cloud.ClusterState;
|
||||||
import org.apache.solr.common.cloud.ZkStateReader;
|
import org.apache.solr.common.cloud.ZkStateReader;
|
||||||
|
@ -50,6 +51,7 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid
|
||||||
volatile Set<String> liveNodes;
|
volatile Set<String> liveNodes;
|
||||||
long liveNodesTimestamp = 0;
|
long liveNodesTimestamp = 0;
|
||||||
volatile Map<String, List<String>> aliases;
|
volatile Map<String, List<String>> aliases;
|
||||||
|
volatile Map<String, Map<String, String>> aliasProperties;
|
||||||
long aliasesTimestamp = 0;
|
long aliasesTimestamp = 0;
|
||||||
|
|
||||||
private int cacheTimeout = 5; // the liveNodes and aliases cache will be invalidated after 5 secs
|
private int cacheTimeout = 5; // the liveNodes and aliases cache will be invalidated after 5 secs
|
||||||
|
@ -212,7 +214,9 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid
|
||||||
String baseUrl = Utils.getBaseUrlForNodeName(nodeName, urlScheme);
|
String baseUrl = Utils.getBaseUrlForNodeName(nodeName, urlScheme);
|
||||||
try (SolrClient client = getSolrClient(baseUrl)) {
|
try (SolrClient client = getSolrClient(baseUrl)) {
|
||||||
|
|
||||||
this.aliases = new CollectionAdminRequest.ListAliases().process(client).getAliasesAsLists();
|
CollectionAdminResponse response = new CollectionAdminRequest.ListAliases().process(client);
|
||||||
|
this.aliases = response.getAliasesAsLists();
|
||||||
|
this.aliasProperties = response.getAliasProperties(); // side-effect
|
||||||
this.aliasesTimestamp = System.nanoTime();
|
this.aliasesTimestamp = System.nanoTime();
|
||||||
return Collections.unmodifiableMap(this.aliases);
|
return Collections.unmodifiableMap(this.aliases);
|
||||||
} catch (SolrServerException | RemoteSolrException | IOException e) {
|
} catch (SolrServerException | RemoteSolrException | IOException e) {
|
||||||
|
@ -221,6 +225,7 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid
|
||||||
log.warn("LISTALIASES not found, possibly using older Solr server. Aliases won't work"
|
log.warn("LISTALIASES not found, possibly using older Solr server. Aliases won't work"
|
||||||
+ " unless you re-create the CloudSolrClient using zkHost(s) or upgrade Solr server", e);
|
+ " unless you re-create the CloudSolrClient using zkHost(s) or upgrade Solr server", e);
|
||||||
this.aliases = Collections.emptyMap();
|
this.aliases = Collections.emptyMap();
|
||||||
|
this.aliasProperties = Collections.emptyMap();
|
||||||
this.aliasesTimestamp = System.nanoTime();
|
this.aliasesTimestamp = System.nanoTime();
|
||||||
return aliases;
|
return aliases;
|
||||||
}
|
}
|
||||||
|
@ -238,6 +243,12 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAliasProperties(String alias) {
|
||||||
|
getAliases(false);
|
||||||
|
return Collections.unmodifiableMap(aliasProperties.get(alias));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClusterState getClusterState() throws IOException {
|
public ClusterState getClusterState() throws IOException {
|
||||||
for (String nodeName: liveNodes) {
|
for (String nodeName: liveNodes) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Set;
|
||||||
import org.apache.solr.common.SolrCloseable;
|
import org.apache.solr.common.SolrCloseable;
|
||||||
import org.apache.solr.common.cloud.ClusterState;
|
import org.apache.solr.common.cloud.ClusterState;
|
||||||
import org.apache.solr.common.cloud.DocCollection;
|
import org.apache.solr.common.cloud.DocCollection;
|
||||||
|
import org.apache.solr.common.params.CollectionAdminParams;
|
||||||
|
|
||||||
public interface ClusterStateProvider extends SolrCloseable {
|
public interface ClusterStateProvider extends SolrCloseable {
|
||||||
|
|
||||||
|
@ -44,6 +45,11 @@ public interface ClusterStateProvider extends SolrCloseable {
|
||||||
*/
|
*/
|
||||||
List<String> resolveAlias(String alias);
|
List<String> resolveAlias(String alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return alias properties, or an empty map if the alias has no properties.
|
||||||
|
*/
|
||||||
|
Map<String, String> getAliasProperties(String alias);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a collection alias, return a single collection it points to, or the original name if it's not an
|
* Given a collection alias, return a single collection it points to, or the original name if it's not an
|
||||||
* alias.
|
* alias.
|
||||||
|
@ -57,6 +63,13 @@ public interface ClusterStateProvider extends SolrCloseable {
|
||||||
return aliases.get(0);
|
return aliases.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if an alias exists and is a routed alias, false otherwise.
|
||||||
|
*/
|
||||||
|
default boolean isRoutedAlias(String alias) {
|
||||||
|
return getAliasProperties(alias).entrySet().stream().anyMatch(e -> e.getKey().startsWith(CollectionAdminParams.ROUTER_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain the current cluster state.
|
* Obtain the current cluster state.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -88,6 +88,11 @@ public class ZkClientClusterStateProvider implements ClusterStateProvider {
|
||||||
return getZkStateReader().getAliases().resolveAliases(alias); // if not an alias, returns itself
|
return getZkStateReader().getAliases().resolveAliases(alias); // if not an alias, returns itself
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAliasProperties(String alias) {
|
||||||
|
return getZkStateReader().getAliases().getCollectionAliasProperties(alias);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
|
public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
|
||||||
return getZkStateReader().getAliases().resolveSimpleAlias(alias);
|
return getZkStateReader().getAliases().resolveSimpleAlias(alias);
|
||||||
|
|
|
@ -83,6 +83,14 @@ public class CollectionAdminResponse extends SolrResponseBase
|
||||||
return Aliases.convertMapOfCommaDelimitedToMapOfList(getAliases());
|
return Aliases.convertMapOfCommaDelimitedToMapOfList(getAliases());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, String>> getAliasProperties() {
|
||||||
|
NamedList<Object> response = getResponse();
|
||||||
|
if (response.get("properties") != null) {
|
||||||
|
return ((Map<String, Map<String, String>>)response.get("properties"));
|
||||||
|
}
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Map<String, NamedList<Integer>> getCollectionNodesStatus()
|
public Map<String, NamedList<Integer>> getCollectionNodesStatus()
|
||||||
{
|
{
|
||||||
|
|
|
@ -189,6 +189,20 @@ public class Aliases {
|
||||||
return collectionAliases.containsKey(aliasName);
|
return collectionAliases.containsKey(aliasName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if an alias exists and is a routed alias, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isRoutedAlias(String aliasName) {
|
||||||
|
if (!collectionAliases.containsKey(aliasName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> props = collectionAliasProperties.get(aliasName);
|
||||||
|
if (props == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return props.entrySet().stream().anyMatch(e -> e.getKey().startsWith(CollectionAdminParams.ROUTER_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve an alias that points to a single collection. One level of alias indirection is supported.
|
* Resolve an alias that points to a single collection. One level of alias indirection is supported.
|
||||||
* @param aliasName alias name
|
* @param aliasName alias name
|
||||||
|
|
Loading…
Reference in New Issue