diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionStatusApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionStatusApi.java
new file mode 100644
index 00000000000..d07982cab76
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/CollectionStatusApi.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.api.endpoint;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.CollectionStatusResponse;
+
+/**
+ * V2 API definition for fetching collection metadata
+ *
+ *
This API (GET /v2/collections/collectionName) is analogous to the v1
+ * /admin/collections?action=COLSTATUS command.
+ */
+@Path("/collections/{collectionName}")
+public interface CollectionStatusApi {
+
+ // TODO Query parameters currently match those offered by the v1
+ // /admin/collections?action=COLSTATUS. Should param names be updated/clarified?
+ @GET
+ @Operation(
+ summary = "Fetches metadata about the specified collection",
+ tags = {"collections"})
+ CollectionStatusResponse getCollectionStatus(
+ @Parameter(description = "The name of the collection return metadata for", required = true)
+ @PathParam("collectionName")
+ String collectionName,
+ @Parameter(description = SegmentsApi.CORE_INFO_PARAM_DESC) @QueryParam("coreInfo")
+ Boolean coreInfo,
+ @Parameter(
+ description =
+ "Boolean flag to include metadata and statistics about the segments used by each shard leader. Implicitly set to true by 'fieldInfo' and 'sizeInfo'")
+ @QueryParam("segments")
+ Boolean segments,
+ @Parameter(
+ description =
+ SegmentsApi.FIELD_INFO_PARAM_DESC
+ + " Implicitly sets the 'segments' flag to 'true'")
+ @QueryParam("fieldInfo")
+ Boolean fieldInfo,
+ @Parameter(description = SegmentsApi.RAW_SIZE_PARAM_DESC) @QueryParam("rawSize")
+ Boolean rawSize,
+ @Parameter(description = SegmentsApi.RAW_SIZE_SUMMARY_DESC) @QueryParam("rawSizeSummary")
+ Boolean rawSizeSummary,
+ @Parameter(description = SegmentsApi.RAW_SIZE_DETAILS_DESC) @QueryParam("rawSizeDetails")
+ Boolean rawSizeDetails,
+ @Parameter(description = SegmentsApi.RAW_SIZE_SAMPLING_PERCENT_DESC)
+ @QueryParam("rawSizeSamplingPercent")
+ Float rawSizeSamplingPercent,
+ @Parameter(
+ description =
+ SegmentsApi.SIZE_INFO_PARAM_DESC
+ + ". Implicitly sets the 'segment' flag to 'true'")
+ @QueryParam("sizeInfo")
+ Boolean sizeInfo)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/SegmentsApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/SegmentsApi.java
new file mode 100644
index 00000000000..1f6f089642e
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/SegmentsApi.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.api.endpoint;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.GetSegmentDataResponse;
+import org.apache.solr.client.api.util.CoreApiParameters;
+
+/**
+ * V2 API definition for fetching metadata about a core's segments
+ *
+ *
This API (GET /v2/cores/coreName/segments) is analogous to the v1
+ * /solr/coreName/admin/segments API
+ */
+@Path("/cores/{coreName}/segments")
+public interface SegmentsApi {
+
+ String CORE_INFO_PARAM_DESC =
+ "Boolean flag to include metadata (e.g. index an data directories, IndexWriter configuration, etc.) about each shard leader's core";
+ String FIELD_INFO_PARAM_DESC =
+ "Boolean flag to include statistics about the indexed fields present on each shard leader.";
+ String RAW_SIZE_PARAM_DESC =
+ "Boolean flag to include simple estimates of the disk size taken up by each field (e.g. \"id\", \"_version_\") and by each index data structure (e.g. 'storedFields', 'docValues_numeric').";
+ String RAW_SIZE_SUMMARY_DESC =
+ "Boolean flag to include more involved estimates of the disk size taken up by index data structures, on a per-field basis (e.g. how much data does the \"id\" field contribute to 'storedField' index files). More detail than 'rawSize', less detail than 'rawSizeDetails'.";
+ String RAW_SIZE_DETAILS_DESC =
+ "Boolean flag to include detailed statistics about the disk size taken up by various fields and data structures. More detail than 'rawSize' and 'rawSizeSummary'.";
+ String RAW_SIZE_SAMPLING_PERCENT_DESC =
+ "Percentage (between 0 and 100) of data to read when estimating index size and statistics. Defaults to 5.0 (i.e. 5%).";
+ String SIZE_INFO_PARAM_DESC =
+ "Boolean flag to include information about the largest index files for each Lucene segment.";
+
+ @GET
+ @CoreApiParameters
+ @Operation(
+ summary = "Fetches metadata about the segments in use by the specified core",
+ tags = {"segments"})
+ GetSegmentDataResponse getSegmentData(
+ @Parameter(description = CORE_INFO_PARAM_DESC) @QueryParam("coreInfo") Boolean coreInfo,
+ @Parameter(description = FIELD_INFO_PARAM_DESC) @QueryParam("fieldInfo") Boolean fieldInfo,
+ @Parameter(description = RAW_SIZE_PARAM_DESC) @QueryParam("rawSize") Boolean rawSize,
+ @Parameter(description = RAW_SIZE_SUMMARY_DESC) @QueryParam("rawSizeSummary")
+ Boolean rawSizeSummary,
+ @Parameter(description = RAW_SIZE_DETAILS_DESC) @QueryParam("rawSizeDetails")
+ Boolean rawSizeDetails,
+ @Parameter(description = RAW_SIZE_SAMPLING_PERCENT_DESC) @QueryParam("rawSizeSamplingPercent")
+ Float rawSizeSamplingPercent,
+ @Parameter(description = SIZE_INFO_PARAM_DESC) @QueryParam("sizeInfo") Boolean sizeInfo)
+ throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/CollectionStatusResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CollectionStatusResponse.java
new file mode 100644
index 00000000000..82109edb915
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CollectionStatusResponse.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Response of the CollectionStatusApi.getCollectionStatus() API
+ *
+ *
Note that the corresponding v1 API has a slightly different response format. Users should not
+ * attempt to convert a v1 response into this type.
+ */
+public class CollectionStatusResponse extends SolrJerseyResponse {
+
+ @JsonProperty public String name;
+ @JsonProperty public Integer znodeVersion;
+
+ // TODO - consider 'Instant' once SOLR-17608 is finished
+ @JsonProperty
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER)
+ public Date creationTimeMillis;
+
+ @JsonProperty public CollectionMetadata properties;
+ @JsonProperty public Integer activeShards;
+ @JsonProperty public Integer inactiveShards;
+ @JsonProperty public List schemaNonCompliant;
+
+ @JsonProperty public Map shards;
+
+ // Always present in response
+ public static class CollectionMetadata {
+ @JsonProperty public String configName;
+ @JsonProperty public Integer nrtReplicas;
+ @JsonProperty public Integer pullReplicas;
+ @JsonProperty public Integer tlogReplicas;
+ @JsonProperty public Map router;
+ @JsonProperty public Integer replicationFactor;
+
+ private Map unknownFields = new HashMap<>();
+
+ @JsonAnyGetter
+ public Map unknownProperties() {
+ return unknownFields;
+ }
+
+ @JsonAnySetter
+ public void setUnknownProperty(String field, Object value) {
+ unknownFields.put(field, value);
+ }
+ }
+
+ // Always present in response
+ public static class ShardMetadata {
+ @JsonProperty public String state; // TODO Make this an enum?
+ @JsonProperty public String range;
+ @JsonProperty public ReplicaSummary replicas;
+ @JsonProperty public LeaderSummary leader;
+ }
+
+ // Always present in response
+ public static class ReplicaSummary {
+ @JsonProperty public Integer total;
+ @JsonProperty public Integer active;
+ @JsonProperty public Integer down;
+ @JsonProperty public Integer recovering;
+
+ @JsonProperty("recovery_failed")
+ public Integer recoveryFailed;
+ }
+
+ // Always present in response unless otherwise specified
+ public static class LeaderSummary {
+ @JsonProperty public String coreNode;
+ @JsonProperty public String core;
+ @JsonProperty public Boolean leader;
+
+ @JsonProperty("node_name")
+ public String nodeName;
+
+ @JsonProperty("base_url")
+ public String baseUrl;
+
+ @JsonProperty public String state; // TODO Make this an enum?
+ @JsonProperty public String type; // TODO Make this an enum?
+
+ @JsonProperty("force_set_state")
+ public Boolean forceSetState;
+
+ // Present with coreInfo=true || sizeInfo=true unless otherwise specified
+ @JsonProperty public SegmentInfo segInfos;
+
+ private Map unknownFields = new HashMap<>();
+
+ @JsonAnyGetter
+ public Map unknownProperties() {
+ return unknownFields;
+ }
+
+ @JsonAnySetter
+ public void setUnknownProperty(String field, Object value) {
+ unknownFields.put(field, value);
+ }
+ }
+
+ // Present with segments=true || coreInfo=true || sizeInfo=true || fieldInfo=true unless otherwise
+ // specified
+
+ /**
+ * Same properties as {@link GetSegmentDataResponse}, but uses a different class to avoid
+ * inheriting "responseHeader", etc.
+ */
+ public static class SegmentInfo {
+ @JsonProperty public GetSegmentDataResponse.SegmentSummary info;
+
+ @JsonProperty public Map runningMerges;
+
+ // Present with segments=true || sizeInfo=true || fieldInfo=true
+ @JsonProperty public Map segments;
+
+ // Present with rawSize=true
+ @JsonProperty public GetSegmentDataResponse.RawSize rawSize;
+
+ // Present only with fieldInfo=true
+ @JsonProperty public List fieldInfoLegend;
+ }
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/GetSegmentDataResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/GetSegmentDataResponse.java
new file mode 100644
index 00000000000..b5e3714bfd3
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/GetSegmentDataResponse.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Response for {@link org.apache.solr.client.api.endpoint.SegmentsApi#getSegmentData(Boolean,
+ * Boolean, Boolean, Boolean, Boolean, Float, Boolean)} API
+ */
+public class GetSegmentDataResponse extends SolrJerseyResponse {
+ @JsonProperty public SegmentSummary info;
+
+ @JsonProperty public Map runningMerges;
+
+ @JsonProperty public Map segments;
+
+ // Present only with fieldInfo=true
+ @JsonProperty public List fieldInfoLegend;
+
+ // Present with rawSize=true
+ @JsonProperty public RawSize rawSize;
+
+ // Always present in response
+ public static class SegmentSummary {
+ @JsonProperty public String minSegmentLuceneVersion;
+ @JsonProperty public String commitLuceneVersion;
+ @JsonProperty public Integer numSegments;
+ @JsonProperty public String segmentsFileName;
+ @JsonProperty public Integer totalMaxDoc;
+ // Typically keys are 'commitCommandVer' and 'commitTimeMSec'
+ @JsonProperty public Map userData;
+
+ // Present for coreInfo=true only
+ @JsonProperty public CoreSummary core;
+ }
+
+ // Always present in response, provided that the specified core has segments
+ public static class SingleSegmentData {
+ @JsonProperty public String name;
+ @JsonProperty public Integer delCount;
+ @JsonProperty public Integer softDelCount;
+ @JsonProperty public Boolean hasFieldUpdates;
+ @JsonProperty public Long sizeInBytes;
+ @JsonProperty public Integer size;
+
+ // TODO - consider 'Instant' once SOLR-17608 is finished
+ @JsonProperty
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "YYYY-MM-DD'T'hh:mm:ss.S'Z'")
+ public Date age;
+
+ @JsonProperty public String source;
+ @JsonProperty public String version;
+ @JsonProperty public Integer createdVersionMajor;
+ @JsonProperty public String minVersion;
+ @JsonProperty public SegmentDiagnosticInfo diagnostics;
+ @JsonProperty public Map attributes;
+ // Only present when index-sorting is in use
+ @JsonProperty public String sort;
+ @JsonProperty public Boolean mergeCandidate;
+
+ // Present only when fieldInfo=true
+ @JsonProperty public Map fields;
+
+ // Present only when sizeInfo=true
+ @JsonProperty("largestFiles")
+ public Map largestFilesByName;
+ }
+
+ // Always present in response, provided that the specified core has segments
+ public static class SegmentSingleFieldInfo {
+ @JsonProperty public String flags;
+ @JsonProperty public Integer docCount;
+ @JsonProperty public Long termCount;
+ @JsonProperty public Long sumDocFreq;
+ @JsonProperty public Long sumTotalTermFreq;
+ @JsonProperty public String schemaType;
+ @JsonProperty public Map nonCompliant;
+ }
+
+ // Always present in response
+ public static class SegmentDiagnosticInfo {
+ @JsonProperty("os.version")
+ public String osVersion;
+
+ @JsonProperty("lucene.version")
+ public String luceneVersion;
+
+ @JsonProperty public String source;
+
+ // TODO - consider 'Instant' once SOLR-17608 is finished
+ @JsonProperty
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER)
+ public Date timestamp;
+
+ @JsonProperty("java.runtime.version")
+ public String javaRuntimeVersion;
+
+ @JsonProperty public String os;
+
+ @JsonProperty("java.vendor")
+ public String javaVendor;
+
+ @JsonProperty("os.arch")
+ public String osArchitecture;
+
+ private Map additionalDiagnostics = new HashMap<>();
+
+ @JsonAnyGetter
+ public Map getAdditionalDiagnostics() {
+ return additionalDiagnostics;
+ }
+
+ @JsonAnySetter
+ public void getAdditionalDiagnostics(String field, Object value) {
+ additionalDiagnostics.put(field, value);
+ }
+ }
+
+ // Present with coreInfo=true unless otherwise specified
+ public static class CoreSummary {
+ @JsonProperty public String startTime;
+ @JsonProperty public String dataDir;
+ @JsonProperty public String indexDir;
+ @JsonProperty public Double sizeInGB;
+ @JsonProperty public IndexWriterConfigSummary indexWriterConfig;
+ }
+
+ // Present with coreInfo=true unless otherwise specified
+
+ /** A serializable representation of Lucene's "LiveIndexWriterConfig" */
+ public static class IndexWriterConfigSummary {
+ @JsonProperty public String analyzer;
+ @JsonProperty public Double ramBufferSizeMB;
+ @JsonProperty public Integer maxBufferedDocs;
+ @JsonProperty public String mergedSegmentWarmer;
+ @JsonProperty public String delPolicy;
+ @JsonProperty public String commit;
+ @JsonProperty public String openMode;
+ @JsonProperty public String similarity;
+ @JsonProperty public String mergeScheduler;
+ @JsonProperty public String codec;
+ @JsonProperty public String infoStream;
+ @JsonProperty public String mergePolicy;
+ @JsonProperty public Boolean readerPooling;
+ @JsonProperty public Integer perThreadHardLimitMB;
+ @JsonProperty public Boolean useCompoundFile;
+ @JsonProperty public Boolean commitOnClose;
+ @JsonProperty public String indexSort;
+ @JsonProperty public Boolean checkPendingFlushOnUpdate;
+ @JsonProperty public String softDeletesField;
+ @JsonProperty public Long maxFullFlushMergeWaitMillis;
+ @JsonProperty public String leafSorter;
+ @JsonProperty public String eventListener;
+ @JsonProperty public String parentField;
+ @JsonProperty public String writer;
+ }
+
+ // Present with rawSize=true unless otherwise specified
+ public static class RawSize {
+ @JsonProperty public Map fieldsBySize;
+ @JsonProperty public Map typesBySize;
+
+ // Present with rawSizeDetails=true
+ @JsonProperty public Object details;
+
+ // Present with rawSizeSummary=true
+ @JsonProperty public Map summary;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
index d156710a675..1ecb290fa0e 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -188,6 +188,8 @@ public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
Thread.currentThread().setContextClassLoader(core.getResourceLoader().getClassLoader());
this.path = path = path.substring(prefix.length() + pathSegments.get(1).length() + 2);
+ // Core-level API, so populate "collection" template val
+ parts.put(COLLECTION_PROP, origCorename);
Api apiInfo = getApiInfo(core.getRequestHandlers(), path, req.getMethod(), fullPath, parts);
if (isCompositeApi && apiInfo instanceof CompositeApi) {
((CompositeApi) this.api).add(apiInfo);
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ColStatus.java b/solr/core/src/java/org/apache/solr/handler/admin/ColStatus.java
index e7fe44e0fe6..234682e8473 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ColStatus.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ColStatus.java
@@ -25,6 +25,7 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
+import org.apache.solr.client.api.model.GetSegmentDataResponse;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.io.SolrClientCache;
@@ -41,6 +42,8 @@
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.jersey.SolrJacksonMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,12 +83,16 @@ public void getColStatus(NamedList results) {
collections = Collections.singleton(col);
}
boolean withFieldInfo = props.getBool(FIELD_INFO_PROP, false);
- boolean withSegments = props.getBool(SEGMENTS_PROP, false);
boolean withCoreInfo = props.getBool(CORE_INFO_PROP, false);
boolean withSizeInfo = props.getBool(SIZE_INFO_PROP, false);
boolean withRawSizeInfo = props.getBool(RAW_SIZE_PROP, false);
boolean withRawSizeSummary = props.getBool(RAW_SIZE_SUMMARY_PROP, false);
boolean withRawSizeDetails = props.getBool(RAW_SIZE_DETAILS_PROP, false);
+ // FieldInfo and SizeInfo imply segments=true, since they add to the data reported about each
+ // segment
+ boolean withSegments = props.getBool(SEGMENTS_PROP, false);
+ withSegments |= withFieldInfo || withSizeInfo;
+
Object samplingPercentVal = props.get(RAW_SIZE_SAMPLING_PERCENT_PROP);
Float samplingPercent =
samplingPercentVal != null ? Float.parseFloat(String.valueOf(samplingPercentVal)) : null;
@@ -94,6 +101,7 @@ public void getColStatus(NamedList results) {
}
boolean getSegments = false;
if (withFieldInfo
+ || withSegments
|| withSizeInfo
|| withCoreInfo
|| withRawSizeInfo
@@ -196,32 +204,35 @@ public void getColStatus(NamedList results) {
}
QueryRequest req = new QueryRequest(params);
NamedList rsp = client.request(req);
- rsp.remove("responseHeader");
- leaderMap.add("segInfos", rsp);
- NamedList> segs = (NamedList>) rsp.get("segments");
+ final var segmentResponse =
+ SolrJacksonMapper.getObjectMapper().convertValue(rsp, GetSegmentDataResponse.class);
+ segmentResponse.responseHeader = null;
+
+ final var segs = segmentResponse.segments;
if (segs != null) {
- for (Map.Entry entry : segs) {
- NamedList fields =
- (NamedList) ((NamedList) entry.getValue()).get("fields");
- if (fields != null) {
- for (Map.Entry fEntry : fields) {
- Object nc = ((NamedList) fEntry.getValue()).get("nonCompliant");
- if (nc != null) {
+ for (Map.Entry entry :
+ segs.entrySet()) {
+ final var fieldInfoByName = entry.getValue().fields;
+ if (fieldInfoByName != null) {
+ for (Map.Entry fEntry :
+ fieldInfoByName.entrySet()) {
+ if (fEntry.getValue().nonCompliant != null) {
nonCompliant.add(fEntry.getKey());
}
}
}
if (!withFieldInfo) {
- ((NamedList) entry.getValue()).remove("fields");
+ entry.getValue().fields = null;
}
}
}
if (!withSegments) {
- rsp.remove("segments");
+ segmentResponse.segments = null;
}
if (!withFieldInfo) {
- rsp.remove("fieldInfoLegend");
+ segmentResponse.fieldInfoLegend = null;
}
+ leaderMap.add("segInfos", Utils.reflectToMap(segmentResponse));
} catch (SolrServerException | IOException e) {
log.warn("Error getting details of replica segments from {}", url, e);
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index df6ba086d06..aefc1033d5e 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -174,7 +174,7 @@
import org.apache.solr.handler.admin.api.BalanceShardUnique;
import org.apache.solr.handler.admin.api.ClusterProperty;
import org.apache.solr.handler.admin.api.CollectionProperty;
-import org.apache.solr.handler.admin.api.CollectionStatusAPI;
+import org.apache.solr.handler.admin.api.CollectionStatus;
import org.apache.solr.handler.admin.api.CreateAlias;
import org.apache.solr.handler.admin.api.CreateCollection;
import org.apache.solr.handler.admin.api.CreateCollectionBackup;
@@ -539,11 +539,8 @@ public enum CollectionOperation implements CollectionOp {
ColStatus.RAW_SIZE_SAMPLING_PERCENT_PROP,
ColStatus.SIZE_INFO_PROP);
- new ColStatus(
- h.coreContainer.getSolrClientCache(),
- h.coreContainer.getZkController().getZkStateReader().getClusterState(),
- new ZkNodeProps(props))
- .getColStatus(rsp.getValues());
+ CollectionStatus.populateColStatusData(
+ h.coreContainer, new ZkNodeProps(props), rsp.getValues());
return null;
}),
DELETE_OP(
@@ -1360,6 +1357,7 @@ public Collection> getJerseyResources() {
CreateReplica.class,
AddReplicaProperty.class,
BalanceShardUnique.class,
+ CollectionStatus.class,
CreateAlias.class,
CreateCollection.class,
CreateCollectionBackup.class,
@@ -1399,7 +1397,6 @@ public Collection getApis() {
apis.addAll(AnnotatedApi.getApis(new ModifyCollectionAPI(this)));
apis.addAll(AnnotatedApi.getApis(new MoveReplicaAPI(this)));
apis.addAll(AnnotatedApi.getApis(new RebalanceLeadersAPI(this)));
- apis.addAll(AnnotatedApi.getApis(new CollectionStatusAPI(this)));
return apis;
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
index 93cdf071a1c..fd1378b6597 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
@@ -16,57 +16,20 @@
*/
package org.apache.solr.handler.admin;
-import static org.apache.lucene.index.IndexOptions.DOCS;
-import static org.apache.lucene.index.IndexOptions.DOCS_AND_FREQS;
-import static org.apache.lucene.index.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS;
-import static org.apache.solr.common.params.CommonParams.NAME;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
+import java.util.Collection;
import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.lucene.index.DocValuesType;
-import org.apache.lucene.index.FieldInfo;
-import org.apache.lucene.index.FieldInfos;
-import org.apache.lucene.index.FilterLeafReader;
-import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.LeafMetaData;
-import org.apache.lucene.index.LeafReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.MergePolicy;
-import org.apache.lucene.index.MergePolicy.MergeSpecification;
-import org.apache.lucene.index.MergePolicy.OneMerge;
-import org.apache.lucene.index.MergeTrigger;
-import org.apache.lucene.index.SegmentCommitInfo;
-import org.apache.lucene.index.SegmentInfos;
-import org.apache.lucene.index.SegmentReader;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.RamUsageEstimator;
-import org.apache.lucene.util.Version;
-import org.apache.solr.common.luke.FieldFlag;
-import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.core.SolrCore;
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.common.params.SolrParams;
import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.admin.api.GetSegmentData;
+import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.schema.SchemaField;
-import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.security.AuthorizationContext;
-import org.apache.solr.update.SolrIndexWriter;
-import org.apache.solr.util.RefCounted;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/** This handler exposes information about last commit generation segments */
public class SegmentsInfoRequestHandler extends RequestHandlerBase {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public static final String FIELD_INFO_PARAM = "fieldInfo";
public static final String CORE_INFO_PARAM = "coreInfo";
@@ -76,385 +39,22 @@ public class SegmentsInfoRequestHandler extends RequestHandlerBase {
public static final String RAW_SIZE_DETAILS_PARAM = "rawSizeDetails";
public static final String RAW_SIZE_SAMPLING_PERCENT_PARAM = "rawSizeSamplingPercent";
- private static final List FI_LEGEND;
-
- static {
- FI_LEGEND =
- Arrays.asList(
- FieldFlag.INDEXED.toString(),
- FieldFlag.DOC_VALUES.toString(),
- "xxx - DocValues type",
- FieldFlag.TERM_VECTOR_STORED.toString(),
- FieldFlag.OMIT_NORMS.toString(),
- FieldFlag.OMIT_TF.toString(),
- FieldFlag.OMIT_POSITIONS.toString(),
- FieldFlag.STORE_OFFSETS_WITH_POSITIONS.toString(),
- "p - field has payloads",
- "s - field uses soft deletes",
- ":x:x:x - point data dim : index dim : num bytes");
- }
-
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- getSegmentsInfo(req, rsp);
- rsp.setHttpCaching(false);
- }
-
- private static final double GB = 1024.0 * 1024.0 * 1024.0;
-
- private void getSegmentsInfo(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- boolean withFieldInfo = req.getParams().getBool(FIELD_INFO_PARAM, false);
- boolean withCoreInfo = req.getParams().getBool(CORE_INFO_PARAM, false);
- boolean withSizeInfo = req.getParams().getBool(SIZE_INFO_PARAM, false);
- boolean withRawSizeInfo = req.getParams().getBool(RAW_SIZE_PARAM, false);
- boolean withRawSizeSummary = req.getParams().getBool(RAW_SIZE_SUMMARY_PARAM, false);
- boolean withRawSizeDetails = req.getParams().getBool(RAW_SIZE_DETAILS_PARAM, false);
- if (withRawSizeSummary || withRawSizeDetails) {
- withRawSizeInfo = true;
- }
- SolrIndexSearcher searcher = req.getSearcher();
-
- SegmentInfos infos = SegmentInfos.readLatestCommit(searcher.getIndexReader().directory());
-
- SimpleOrderedMap segmentInfos = new SimpleOrderedMap<>();
-
- SolrCore core = req.getCore();
- SimpleOrderedMap infosInfo = new SimpleOrderedMap<>();
- Version minVersion = infos.getMinSegmentLuceneVersion();
- if (minVersion != null) {
- infosInfo.add("minSegmentLuceneVersion", minVersion.toString());
- }
- Version commitVersion = infos.getCommitLuceneVersion();
- if (commitVersion != null) {
- infosInfo.add("commitLuceneVersion", commitVersion.toString());
- }
- infosInfo.add("numSegments", infos.size());
- infosInfo.add("segmentsFileName", infos.getSegmentsFileName());
- infosInfo.add("totalMaxDoc", infos.totalMaxDoc());
- infosInfo.add("userData", infos.userData);
- if (withCoreInfo) {
- SimpleOrderedMap coreInfo = new SimpleOrderedMap<>();
- infosInfo.add("core", coreInfo);
- coreInfo.add(
- "startTime", core.getStartTimeStamp().getTime() + "(" + core.getStartTimeStamp() + ")");
- coreInfo.add("dataDir", core.getDataDir());
- coreInfo.add("indexDir", core.getIndexDir());
- coreInfo.add("sizeInGB", (double) core.getIndexSize() / GB);
-
- RefCounted iwRef = core.getSolrCoreState().getIndexWriter(core);
- if (iwRef != null) {
- try {
- IndexWriter iw = iwRef.get();
- String iwConfigStr = iw.getConfig().toString();
- SimpleOrderedMap iwConfig = new SimpleOrderedMap<>();
- // meh ...
- String[] lines = iwConfigStr.split("\\n");
- for (String line : lines) {
- String[] parts = line.split("=");
- if (parts.length < 2) {
- continue;
- }
- iwConfig.add(parts[0], parts[1]);
- }
- coreInfo.add("indexWriterConfig", iwConfig);
- } finally {
- iwRef.decref();
- }
- }
- }
- SimpleOrderedMap segmentInfo;
- List sortable = new ArrayList<>(infos.asList());
- // Order by the number of live docs. The display is logarithmic so it is a little jumbled
- // visually
- sortable.sort(
- (s1, s2) -> (s2.info.maxDoc() - s2.getDelCount()) - (s1.info.maxDoc() - s1.getDelCount()));
-
- List mergeCandidates = new ArrayList<>();
- SimpleOrderedMap runningMerges = getMergeInformation(req, infos, mergeCandidates);
- List leafContexts = searcher.getIndexReader().leaves();
- IndexSchema schema = req.getSchema();
- for (SegmentCommitInfo segmentCommitInfo : sortable) {
- segmentInfo =
- getSegmentInfo(segmentCommitInfo, withSizeInfo, withFieldInfo, leafContexts, schema);
- if (mergeCandidates.contains(segmentCommitInfo.info.name)) {
- segmentInfo.add("mergeCandidate", true);
- }
- segmentInfos.add((String) segmentInfo.get(NAME), segmentInfo);
- }
-
- rsp.add("info", infosInfo);
- if (runningMerges.size() > 0) {
- rsp.add("runningMerges", runningMerges);
- }
- if (withFieldInfo) {
- rsp.add("fieldInfoLegend", FI_LEGEND);
- }
- rsp.add("segments", segmentInfos);
- if (withRawSizeInfo) {
- IndexSizeEstimator estimator =
- new IndexSizeEstimator(
- searcher.getRawReader(), 20, 100, withRawSizeSummary, withRawSizeDetails);
- Object samplingPercentVal = req.getParams().get(RAW_SIZE_SAMPLING_PERCENT_PARAM);
- if (samplingPercentVal != null) {
- estimator.setSamplingPercent(Float.parseFloat(String.valueOf(samplingPercentVal)));
- }
- IndexSizeEstimator.Estimate estimate = estimator.estimate();
- SimpleOrderedMap estimateMap = new SimpleOrderedMap<>();
- // make the units more user-friendly
- estimateMap.add(IndexSizeEstimator.FIELDS_BY_SIZE, estimate.getHumanReadableFieldsBySize());
- estimateMap.add(IndexSizeEstimator.TYPES_BY_SIZE, estimate.getHumanReadableTypesBySize());
- if (estimate.getSummary() != null) {
- estimateMap.add(IndexSizeEstimator.SUMMARY, estimate.getSummary());
- }
- if (estimate.getDetails() != null) {
- estimateMap.add(IndexSizeEstimator.DETAILS, estimate.getDetails());
- }
- rsp.add("rawSize", estimateMap);
- }
- }
-
- private SimpleOrderedMap getSegmentInfo(
- SegmentCommitInfo segmentCommitInfo,
- boolean withSizeInfo,
- boolean withFieldInfos,
- List leafContexts,
- IndexSchema schema)
- throws IOException {
- SimpleOrderedMap segmentInfoMap = new SimpleOrderedMap<>();
-
- segmentInfoMap.add(NAME, segmentCommitInfo.info.name);
- segmentInfoMap.add("delCount", segmentCommitInfo.getDelCount());
- segmentInfoMap.add("softDelCount", segmentCommitInfo.getSoftDelCount());
- segmentInfoMap.add("hasFieldUpdates", segmentCommitInfo.hasFieldUpdates());
- segmentInfoMap.add("sizeInBytes", segmentCommitInfo.sizeInBytes());
- segmentInfoMap.add("size", segmentCommitInfo.info.maxDoc());
- Long timestamp = Long.parseLong(segmentCommitInfo.info.getDiagnostics().get("timestamp"));
- segmentInfoMap.add("age", new Date(timestamp));
- segmentInfoMap.add("source", segmentCommitInfo.info.getDiagnostics().get("source"));
- segmentInfoMap.add("version", segmentCommitInfo.info.getVersion().toString());
- // don't open a new SegmentReader - try to find the right one from the leaf contexts
- SegmentReader seg = null;
- for (LeafReaderContext lrc : leafContexts) {
- LeafReader leafReader = lrc.reader();
- leafReader = FilterLeafReader.unwrap(leafReader);
- if (leafReader instanceof SegmentReader sr) {
- if (sr.getSegmentInfo().info.equals(segmentCommitInfo.info)) {
- seg = sr;
- break;
- }
- }
- }
- if (seg != null) {
- LeafMetaData metaData = seg.getMetaData();
- if (metaData != null) {
- segmentInfoMap.add("createdVersionMajor", metaData.getCreatedVersionMajor());
- segmentInfoMap.add("minVersion", metaData.getMinVersion().toString());
- if (metaData.getSort() != null) {
- segmentInfoMap.add("sort", metaData.getSort().toString());
- }
- }
- }
- if (!segmentCommitInfo.info.getDiagnostics().isEmpty()) {
- segmentInfoMap.add("diagnostics", segmentCommitInfo.info.getDiagnostics());
- }
- if (!segmentCommitInfo.info.getAttributes().isEmpty()) {
- segmentInfoMap.add("attributes", segmentCommitInfo.info.getAttributes());
- }
- if (withSizeInfo) {
- Directory dir = segmentCommitInfo.info.dir;
- List> files =
- segmentCommitInfo.files().stream()
- .map(
- f -> {
- long size = -1;
- try {
- size = dir.fileLength(f);
- } catch (IOException e) {
- }
- return new Pair(f, size);
- })
- .sorted(
- (p1, p2) -> {
- if (p1.second() > p2.second()) {
- return -1;
- } else if (p1.second() < p2.second()) {
- return 1;
- } else {
- return 0;
- }
- })
- .collect(Collectors.toList());
- if (!files.isEmpty()) {
- SimpleOrderedMap topFiles = new SimpleOrderedMap<>();
- for (int i = 0; i < Math.min(files.size(), 5); i++) {
- Pair p = files.get(i);
- topFiles.add(p.first(), RamUsageEstimator.humanReadableUnits(p.second()));
- }
- segmentInfoMap.add("largestFiles", topFiles);
- }
- }
- if (withFieldInfos) {
- if (seg == null) {
- log.debug(
- "Skipping segment info - not available as a SegmentReader: {}", segmentCommitInfo);
- } else {
- FieldInfos fis = seg.getFieldInfos();
- SimpleOrderedMap fields = new SimpleOrderedMap<>();
- for (FieldInfo fi : fis) {
- fields.add(fi.name, getFieldInfo(seg, fi, schema));
- }
- segmentInfoMap.add("fields", fields);
- }
- }
-
- return segmentInfoMap;
- }
+ final SolrParams params = req.getParams();
+ final GetSegmentData segmentDataApi = new GetSegmentData(req.getCore(), req, rsp);
+ final SolrJerseyResponse response =
+ segmentDataApi.getSegmentData(
+ params.getBool(CORE_INFO_PARAM),
+ params.getBool(FIELD_INFO_PARAM),
+ params.getBool(RAW_SIZE_PARAM),
+ params.getBool(RAW_SIZE_SUMMARY_PARAM),
+ params.getBool(RAW_SIZE_DETAILS_PARAM),
+ params.getFloat(RAW_SIZE_SAMPLING_PERCENT_PARAM),
+ params.getBool(SIZE_INFO_PARAM));
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
- private SimpleOrderedMap getFieldInfo(
- SegmentReader reader, FieldInfo fi, IndexSchema schema) {
- SimpleOrderedMap fieldFlags = new SimpleOrderedMap<>();
- StringBuilder flags = new StringBuilder();
- IndexOptions opts = fi.getIndexOptions();
- flags.append((opts != IndexOptions.NONE) ? FieldFlag.INDEXED.getAbbreviation() : '-');
- DocValuesType dvt = fi.getDocValuesType();
- if (dvt != DocValuesType.NONE) {
- flags.append(FieldFlag.DOC_VALUES.getAbbreviation());
- switch (dvt) {
- case NUMERIC:
- flags.append("num");
- break;
- case BINARY:
- flags.append("bin");
- break;
- case SORTED:
- flags.append("srt");
- break;
- case SORTED_NUMERIC:
- flags.append("srn");
- break;
- case SORTED_SET:
- flags.append("srs");
- break;
- default:
- flags.append("???"); // should not happen
- }
- } else {
- flags.append("----");
- }
- flags.append((fi.hasVectors()) ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-');
- flags.append((fi.omitsNorms()) ? FieldFlag.OMIT_NORMS.getAbbreviation() : '-');
-
- flags.append((DOCS == opts) ? FieldFlag.OMIT_TF.getAbbreviation() : '-');
-
- flags.append((DOCS_AND_FREQS == opts) ? FieldFlag.OMIT_POSITIONS.getAbbreviation() : '-');
-
- flags.append(
- (DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS == opts)
- ? FieldFlag.STORE_OFFSETS_WITH_POSITIONS.getAbbreviation()
- : '-');
-
- flags.append((fi.hasPayloads() ? "p" : "-"));
- flags.append((fi.isSoftDeletesField() ? "s" : "-"));
- if (fi.getPointDimensionCount() > 0 || fi.getPointIndexDimensionCount() > 0) {
- flags.append(":");
- flags.append(fi.getPointDimensionCount()).append(':');
- flags.append(fi.getPointIndexDimensionCount()).append(':');
- flags.append(fi.getPointNumBytes());
- }
-
- fieldFlags.add("flags", flags.toString());
- try {
- Terms terms = reader.terms(fi.name);
- if (terms != null) {
- fieldFlags.add("docCount", terms.getDocCount());
- fieldFlags.add("termCount", terms.size());
- fieldFlags.add("sumDocFreq", terms.getSumDocFreq());
- fieldFlags.add("sumTotalTermFreq", terms.getSumTotalTermFreq());
- }
- } catch (Exception e) {
- log.debug("Exception retrieving term stats for field {}", fi.name, e);
- }
-
- // probably too much detail?
- // Map attributes = fi.attributes();
- // if (!attributes.isEmpty()) {
- // fieldFlags.add("attributes", attributes);
- // }
-
- // check compliance of the index with the current schema
- SchemaField sf = schema.getFieldOrNull(fi.name);
- boolean hasPoints = fi.getPointDimensionCount() > 0 || fi.getPointIndexDimensionCount() > 0;
-
- if (sf != null) {
- fieldFlags.add("schemaType", sf.getType().getTypeName());
- SimpleOrderedMap nonCompliant = new SimpleOrderedMap<>();
- if (sf.hasDocValues()
- && fi.getDocValuesType() == DocValuesType.NONE
- && fi.getIndexOptions() != IndexOptions.NONE) {
- nonCompliant.add(
- "docValues", "schema=" + sf.getType().getUninversionType(sf) + ", segment=false");
- }
- if (!sf.hasDocValues() && fi.getDocValuesType() != DocValuesType.NONE) {
- nonCompliant.add("docValues", "schema=false, segment=" + fi.getDocValuesType().toString());
- }
- if (!sf.isPolyField()) { // difficult to find all sub-fields in a general way
- if (sf.indexed() != ((fi.getIndexOptions() != IndexOptions.NONE) || hasPoints)) {
- nonCompliant.add(
- "indexed", "schema=" + sf.indexed() + ", segment=" + fi.getIndexOptions());
- }
- }
- if (!hasPoints && (sf.omitNorms() != fi.omitsNorms())) {
- nonCompliant.add("omitNorms", "schema=" + sf.omitNorms() + ", segment=" + fi.omitsNorms());
- }
- if (sf.storeTermVector() != fi.hasVectors()) {
- nonCompliant.add(
- "termVectors", "schema=" + sf.storeTermVector() + ", segment=" + fi.hasVectors());
- }
- if (sf.storeOffsetsWithPositions()
- != (fi.getIndexOptions() == IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)) {
- nonCompliant.add(
- "storeOffsetsWithPositions",
- "schema=" + sf.storeOffsetsWithPositions() + ", segment=" + fi.getIndexOptions());
- }
-
- if (nonCompliant.size() > 0) {
- nonCompliant.add("schemaField", sf.toString());
- fieldFlags.add("nonCompliant", nonCompliant);
- }
- } else {
- fieldFlags.add("schemaType", "(UNKNOWN)");
- }
- return fieldFlags;
- }
-
- // returns a map of currently running merges, and populates a list of candidate segments for merge
- private SimpleOrderedMap getMergeInformation(
- SolrQueryRequest req, SegmentInfos infos, List mergeCandidates) throws IOException {
- SimpleOrderedMap result = new SimpleOrderedMap<>();
- RefCounted refCounted =
- req.getCore().getSolrCoreState().getIndexWriter(req.getCore());
- try {
- IndexWriter indexWriter = refCounted.get();
- if (indexWriter instanceof SolrIndexWriter) {
- result.addAll(((SolrIndexWriter) indexWriter).getRunningMerges());
- }
- // get chosen merge policy
- MergePolicy mp = indexWriter.getConfig().getMergePolicy();
- // Find merges
- MergeSpecification findMerges = mp.findMerges(MergeTrigger.EXPLICIT, infos, indexWriter);
- if (findMerges != null && findMerges.merges != null && findMerges.merges.size() > 0) {
- for (OneMerge merge : findMerges.merges) {
- // TODO: add merge grouping
- for (SegmentCommitInfo mergeSegmentInfo : merge.segments) {
- mergeCandidates.add(mergeSegmentInfo.info.name);
- }
- }
- }
-
- return result;
- } finally {
- refCounted.decref();
- }
+ rsp.setHttpCaching(false);
}
@Override
@@ -471,4 +71,14 @@ public Category getCategory() {
public Name getPermissionName(AuthorizationContext request) {
return Name.METRICS_READ_PERM;
}
+
+ @Override
+ public Boolean registerV2() {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ public Collection> getJerseyResources() {
+ return List.of(GetSegmentData.class);
+ }
}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatus.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatus.java
new file mode 100644
index 00000000000..f80b6363071
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatus.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
+
+import jakarta.inject.Inject;
+import org.apache.solr.client.api.endpoint.CollectionStatusApi;
+import org.apache.solr.client.api.model.CollectionStatusResponse;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.ColStatus;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.jersey.SolrJacksonMapper;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.PermissionNameProvider;
+
+/** V2 API implementation for {@link CollectionStatusApi}. */
+public class CollectionStatus extends AdminAPIBase implements CollectionStatusApi {
+
+ @Inject
+ public CollectionStatus(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ @Override
+ @PermissionName(PermissionNameProvider.Name.COLL_READ_PERM)
+ public CollectionStatusResponse getCollectionStatus(
+ String collectionName,
+ Boolean coreInfo,
+ Boolean segments,
+ Boolean fieldInfo,
+ Boolean rawSize,
+ Boolean rawSizeSummary,
+ Boolean rawSizeDetails,
+ Float rawSizeSamplingPercent,
+ Boolean sizeInfo)
+ throws Exception {
+ recordCollectionForLogAndTracing(collectionName, solrQueryRequest);
+
+ final var params = new ModifiableSolrParams();
+ params.set(COLLECTION_PROP, collectionName);
+ params.setNonNull(ColStatus.CORE_INFO_PROP, coreInfo);
+ params.setNonNull(ColStatus.SEGMENTS_PROP, segments);
+ params.setNonNull(ColStatus.FIELD_INFO_PROP, fieldInfo);
+ params.setNonNull(ColStatus.RAW_SIZE_PROP, rawSize);
+ params.setNonNull(ColStatus.RAW_SIZE_SUMMARY_PROP, rawSizeSummary);
+ params.setNonNull(ColStatus.RAW_SIZE_DETAILS_PROP, rawSizeDetails);
+ params.setNonNull(ColStatus.RAW_SIZE_SAMPLING_PERCENT_PROP, rawSizeSamplingPercent);
+ params.setNonNull(ColStatus.SIZE_INFO_PROP, sizeInfo);
+
+ final var nlResponse = new NamedList<>();
+ populateColStatusData(coreContainer, new ZkNodeProps(params), nlResponse);
+
+ // v2 API does not support requesting the status of multiple collections simultaneously as its
+ // counterpart does, and its response looks slightly different as a result. Primarily, the
+ // v2 response eschews a level of nesting that necessitated by the multi-collection nature of
+ // v1. These tweaks are made below before returning.
+ final var colStatusResponse =
+ SolrJacksonMapper.getObjectMapper()
+ .convertValue(nlResponse.get(collectionName), CollectionStatusResponse.class);
+ colStatusResponse.name = collectionName;
+ return colStatusResponse;
+ }
+
+ // TODO Modify ColStatus to produce a CollectionStatusResponse instead of a NL
+ public static void populateColStatusData(
+ CoreContainer coreContainer, ZkNodeProps params, NamedList colStatusSink) {
+ final var colStatusAssembler =
+ new ColStatus(
+ coreContainer.getSolrClientCache(),
+ coreContainer.getZkController().getZkStateReader().getClusterState(),
+ params);
+ colStatusAssembler.getColStatus(colStatusSink);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatusAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatusAPI.java
deleted file mode 100644
index 4b7eabe7226..00000000000
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionStatusAPI.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.handler.admin.api;
-
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
-import static org.apache.solr.common.params.CommonParams.ACTION;
-import static org.apache.solr.common.params.CoreAdminParams.COLLECTION;
-import static org.apache.solr.handler.ClusterAPI.wrapParams;
-import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
-
-import java.lang.invoke.MethodHandles;
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.response.SolrQueryResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * V2 API for displaying basic information about a single collection.
- *
- * This API (GET /v2/collections/collectionName) is analogous to the v1
- * /admin/collections?action=CLUSTERSTATUS&collection=collectionName command.
- */
-public class CollectionStatusAPI {
- private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private final CollectionsHandler collectionsHandler;
-
- public CollectionStatusAPI(CollectionsHandler collectionsHandler) {
- this.collectionsHandler = collectionsHandler;
- }
-
- @EndPoint(
- path = {"/c/{collection}", "/collections/{collection}"},
- method = GET,
- permission = COLL_READ_PERM)
- public void getCollectionStatus(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
- req =
- wrapParams(
- req, // 'req' can have a 'shard' param
- ACTION,
- CollectionParams.CollectionAction.CLUSTERSTATUS.toString(),
- COLLECTION,
- req.getPathTemplateValues().get(ZkStateReader.COLLECTION_PROP));
- collectionsHandler.handleRequestBody(req, rsp);
- }
-}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSegmentData.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSegmentData.java
new file mode 100644
index 00000000000..ceec55ea33f
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSegmentData.java
@@ -0,0 +1,501 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.handler.admin.api;
+
+import static org.apache.lucene.index.IndexOptions.DOCS;
+import static org.apache.lucene.index.IndexOptions.DOCS_AND_FREQS;
+import static org.apache.lucene.index.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS;
+
+import jakarta.inject.Inject;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.lucene.index.DocValuesType;
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.FieldInfos;
+import org.apache.lucene.index.FilterLeafReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.LeafMetaData;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.LiveIndexWriterConfig;
+import org.apache.lucene.index.MergePolicy;
+import org.apache.lucene.index.MergeTrigger;
+import org.apache.lucene.index.SegmentCommitInfo;
+import org.apache.lucene.index.SegmentInfos;
+import org.apache.lucene.index.SegmentReader;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.lucene.util.Version;
+import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.endpoint.SegmentsApi;
+import org.apache.solr.client.api.model.GetSegmentDataResponse;
+import org.apache.solr.common.luke.FieldFlag;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.admin.IndexSizeEstimator;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.jersey.SolrJacksonMapper;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.update.SolrIndexWriter;
+import org.apache.solr.util.RefCounted;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * V2 API implementation for {@link SegmentsApi}
+ *
+ *
Equivalent to the v1 /solr/coreName/admin/segments endpoint.
+ */
+public class GetSegmentData extends JerseyResource implements SegmentsApi {
+
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private static final double GB = 1024.0 * 1024.0 * 1024.0;
+
+ private static final List FI_LEGEND =
+ Arrays.asList(
+ FieldFlag.INDEXED.toString(),
+ FieldFlag.DOC_VALUES.toString(),
+ "xxx - DocValues type",
+ FieldFlag.TERM_VECTOR_STORED.toString(),
+ FieldFlag.OMIT_NORMS.toString(),
+ FieldFlag.OMIT_TF.toString(),
+ FieldFlag.OMIT_POSITIONS.toString(),
+ FieldFlag.STORE_OFFSETS_WITH_POSITIONS.toString(),
+ "p - field has payloads",
+ "s - field uses soft deletes",
+ ":x:x:x - point data dim : index dim : num bytes");
+
+ protected final SolrCore solrCore;
+ protected final SolrQueryRequest solrQueryRequest;
+ protected final SolrQueryResponse solrQueryResponse;
+
+ @Inject
+ public GetSegmentData(SolrCore solrCore, SolrQueryRequest req, SolrQueryResponse rsp) {
+ this.solrCore = solrCore;
+ this.solrQueryRequest = req;
+ this.solrQueryResponse = rsp;
+ }
+
+ @Override
+ @PermissionName(PermissionNameProvider.Name.METRICS_READ_PERM)
+ public GetSegmentDataResponse getSegmentData(
+ Boolean coreInfo,
+ Boolean fieldInfo,
+ Boolean rawSize,
+ Boolean rawSizeSummary,
+ Boolean rawSizeDetails,
+ Float rawSizeSamplingPercent,
+ Boolean sizeInfo)
+ throws Exception {
+ boolean withFieldInfo = Boolean.TRUE.equals(fieldInfo);
+ boolean withCoreInfo = Boolean.TRUE.equals(coreInfo);
+ boolean withSizeInfo = Boolean.TRUE.equals(sizeInfo);
+ boolean withRawSizeInfo = Boolean.TRUE.equals(rawSize);
+ boolean withRawSizeSummary = Boolean.TRUE.equals(rawSizeSummary);
+ boolean withRawSizeDetails = Boolean.TRUE.equals(rawSizeDetails);
+ if (withRawSizeSummary || withRawSizeDetails) {
+ withRawSizeInfo = true;
+ }
+ SolrIndexSearcher searcher = solrQueryRequest.getSearcher();
+ SolrCore core = solrQueryRequest.getCore();
+
+ final var response = new GetSegmentDataResponse();
+
+ SegmentInfos infos = SegmentInfos.readLatestCommit(searcher.getIndexReader().directory());
+ response.info = new GetSegmentDataResponse.SegmentSummary();
+ Version minVersion = infos.getMinSegmentLuceneVersion();
+ if (minVersion != null) {
+ response.info.minSegmentLuceneVersion = minVersion.toString();
+ }
+ Version commitVersion = infos.getCommitLuceneVersion();
+ if (commitVersion != null) {
+ response.info.commitLuceneVersion = commitVersion.toString();
+ }
+ response.info.numSegments = infos.size();
+ response.info.segmentsFileName = infos.getSegmentsFileName();
+ response.info.totalMaxDoc = infos.totalMaxDoc();
+ response.info.userData = infos.userData;
+
+ if (withCoreInfo) {
+ final var coreSummary = new GetSegmentDataResponse.CoreSummary();
+ response.info.core = coreSummary;
+ coreSummary.startTime =
+ core.getStartTimeStamp().getTime() + "(" + core.getStartTimeStamp() + ")";
+ coreSummary.dataDir = core.getDataDir();
+ coreSummary.indexDir = core.getIndexDir();
+ coreSummary.sizeInGB = (double) core.getIndexSize() / GB;
+
+ RefCounted iwRef = core.getSolrCoreState().getIndexWriter(core);
+ if (iwRef != null) {
+ try {
+ IndexWriter iw = iwRef.get();
+ coreSummary.indexWriterConfig = convertIndexWriterConfigToResponse(iw.getConfig());
+ } finally {
+ iwRef.decref();
+ }
+ }
+ }
+
+ List sortable = new ArrayList<>(infos.asList());
+ // Order by the number of live docs. The display is logarithmic so it is a little jumbled
+ // visually
+ sortable.sort(
+ (s1, s2) -> (s2.info.maxDoc() - s2.getDelCount()) - (s1.info.maxDoc() - s1.getDelCount()));
+
+ List mergeCandidates = new ArrayList<>();
+ final var runningMerges = getMergeInformation(solrQueryRequest, infos, mergeCandidates);
+ List leafContexts = searcher.getIndexReader().leaves();
+ IndexSchema schema = solrQueryRequest.getSchema();
+ response.segments = new HashMap<>();
+ for (SegmentCommitInfo segmentCommitInfo : sortable) {
+ final var singleSegmentData =
+ getSegmentInfo(segmentCommitInfo, withSizeInfo, withFieldInfo, leafContexts, schema);
+ if (mergeCandidates.contains(segmentCommitInfo.info.name)) {
+ singleSegmentData.mergeCandidate = true;
+ }
+ response.segments.put(singleSegmentData.name, singleSegmentData);
+ }
+
+ if (runningMerges.size() > 0) {
+ response.runningMerges = runningMerges;
+ }
+ if (withFieldInfo) {
+ response.fieldInfoLegend = FI_LEGEND;
+ }
+
+ if (withRawSizeInfo) {
+ IndexSizeEstimator estimator =
+ new IndexSizeEstimator(
+ searcher.getRawReader(), 20, 100, withRawSizeSummary, withRawSizeDetails);
+ if (rawSizeSamplingPercent != null) {
+ estimator.setSamplingPercent(rawSizeSamplingPercent);
+ }
+ IndexSizeEstimator.Estimate estimate = estimator.estimate();
+ final var rawSizeResponse = new GetSegmentDataResponse.RawSize();
+ // make the units more user-friendly
+ rawSizeResponse.fieldsBySize = estimate.getHumanReadableFieldsBySize();
+ rawSizeResponse.typesBySize = estimate.getHumanReadableTypesBySize();
+ if (estimate.getSummary() != null) {
+ rawSizeResponse.summary = estimate.getSummary();
+ }
+ if (estimate.getDetails() != null) {
+ rawSizeResponse.details = estimate.getDetails();
+ }
+ response.rawSize = rawSizeResponse;
+ }
+
+ return response;
+ }
+
+ /**
+ * Converts Lucene's IndexWriter configuration object into a response type fit for serialization
+ *
+ * Based on {@link LiveIndexWriterConfig#toString()} for legacy reasons.
+ *
+ * @param iwConfig the Lucene configuration object to convert
+ */
+ private GetSegmentDataResponse.IndexWriterConfigSummary convertIndexWriterConfigToResponse(
+ LiveIndexWriterConfig iwConfig) {
+ final var iwConfigResponse = new GetSegmentDataResponse.IndexWriterConfigSummary();
+ iwConfigResponse.analyzer =
+ iwConfig.getAnalyzer() != null ? iwConfig.getAnalyzer().getClass().getName() : "null";
+ iwConfigResponse.ramBufferSizeMB = iwConfig.getRAMBufferSizeMB();
+ iwConfigResponse.maxBufferedDocs = iwConfig.getMaxBufferedDocs();
+ iwConfigResponse.mergedSegmentWarmer = String.valueOf(iwConfig.getMergedSegmentWarmer());
+ iwConfigResponse.delPolicy = iwConfig.getIndexDeletionPolicy().getClass().getName();
+ iwConfigResponse.commit = String.valueOf(iwConfig.getIndexCommit());
+ iwConfigResponse.openMode = String.valueOf(iwConfig.getOpenMode());
+ iwConfigResponse.similarity = iwConfig.getSimilarity().getClass().getName();
+ iwConfigResponse.mergeScheduler = String.valueOf(iwConfig.getMergeScheduler());
+ iwConfigResponse.codec = String.valueOf(iwConfig.getCodec());
+ iwConfigResponse.infoStream = iwConfig.getInfoStream().getClass().getName();
+ iwConfigResponse.mergePolicy = String.valueOf(iwConfig.getMergePolicy());
+ iwConfigResponse.readerPooling = iwConfig.getReaderPooling();
+ iwConfigResponse.perThreadHardLimitMB = iwConfig.getRAMPerThreadHardLimitMB();
+ iwConfigResponse.useCompoundFile = iwConfig.getUseCompoundFile();
+ iwConfigResponse.commitOnClose = iwConfig.getCommitOnClose();
+ iwConfigResponse.indexSort = String.valueOf(iwConfig.getIndexSort());
+ iwConfigResponse.checkPendingFlushOnUpdate = iwConfig.isCheckPendingFlushOnUpdate();
+ iwConfigResponse.softDeletesField = iwConfig.getSoftDeletesField();
+ iwConfigResponse.maxFullFlushMergeWaitMillis = iwConfig.getMaxFullFlushMergeWaitMillis();
+ iwConfigResponse.leafSorter = String.valueOf(iwConfig.getLeafSorter());
+ iwConfigResponse.eventListener = String.valueOf(iwConfig.getIndexWriterEventListener());
+ iwConfigResponse.parentField = iwConfig.getParentField();
+ return iwConfigResponse;
+ }
+
+ // returns a map of currently running merges, and populates a list of candidate segments for merge
+ private Map getMergeInformation(
+ SolrQueryRequest req, SegmentInfos infos, List mergeCandidates) throws IOException {
+ final var result = new HashMap();
+ RefCounted refCounted =
+ req.getCore().getSolrCoreState().getIndexWriter(req.getCore());
+ try {
+ IndexWriter indexWriter = refCounted.get();
+ if (indexWriter instanceof SolrIndexWriter) {
+ result.putAll(((SolrIndexWriter) indexWriter).getRunningMerges());
+ }
+ // get chosen merge policy
+ MergePolicy mp = indexWriter.getConfig().getMergePolicy();
+ // Find merges
+ MergePolicy.MergeSpecification findMerges =
+ mp.findMerges(MergeTrigger.EXPLICIT, infos, indexWriter);
+ if (findMerges != null && findMerges.merges != null && findMerges.merges.size() > 0) {
+ for (MergePolicy.OneMerge merge : findMerges.merges) {
+ // TODO: add merge grouping
+ for (SegmentCommitInfo mergeSegmentInfo : merge.segments) {
+ mergeCandidates.add(mergeSegmentInfo.info.name);
+ }
+ }
+ }
+
+ return result;
+ } finally {
+ refCounted.decref();
+ }
+ }
+
+ private GetSegmentDataResponse.SingleSegmentData getSegmentInfo(
+ SegmentCommitInfo segmentCommitInfo,
+ boolean withSizeInfo,
+ boolean withFieldInfos,
+ List leafContexts,
+ IndexSchema schema)
+ throws IOException {
+ final var segmentInfo = new GetSegmentDataResponse.SingleSegmentData();
+ segmentInfo.name = segmentCommitInfo.info.name;
+ segmentInfo.delCount = segmentCommitInfo.getDelCount();
+ segmentInfo.softDelCount = segmentCommitInfo.getSoftDelCount();
+ segmentInfo.hasFieldUpdates = segmentCommitInfo.hasFieldUpdates();
+ segmentInfo.sizeInBytes = segmentCommitInfo.sizeInBytes();
+ segmentInfo.size = segmentCommitInfo.info.maxDoc();
+ Long timestamp = Long.parseLong(segmentCommitInfo.info.getDiagnostics().get("timestamp"));
+ segmentInfo.age = new Date(timestamp);
+ segmentInfo.source = segmentCommitInfo.info.getDiagnostics().get("source");
+ segmentInfo.version = segmentCommitInfo.info.getVersion().toString();
+
+ // don't open a new SegmentReader - try to find the right one from the leaf contexts
+ SegmentReader seg = null;
+ for (LeafReaderContext lrc : leafContexts) {
+ LeafReader leafReader = lrc.reader();
+ leafReader = FilterLeafReader.unwrap(leafReader);
+ if (leafReader instanceof SegmentReader sr) {
+ if (sr.getSegmentInfo().info.equals(segmentCommitInfo.info)) {
+ seg = sr;
+ break;
+ }
+ }
+ }
+ if (seg != null) {
+ LeafMetaData metaData = seg.getMetaData();
+ if (metaData != null) {
+ segmentInfo.createdVersionMajor = metaData.getCreatedVersionMajor();
+ segmentInfo.minVersion = metaData.getMinVersion().toString();
+ if (metaData.getSort() != null) {
+ segmentInfo.sort = metaData.getSort().toString();
+ }
+ }
+ }
+
+ if (!segmentCommitInfo.info.getDiagnostics().isEmpty()) {
+ segmentInfo.diagnostics =
+ SolrJacksonMapper.getObjectMapper()
+ .convertValue(
+ segmentCommitInfo.info.getDiagnostics(),
+ GetSegmentDataResponse.SegmentDiagnosticInfo.class);
+ }
+ if (!segmentCommitInfo.info.getAttributes().isEmpty()) {
+ segmentInfo.attributes = segmentCommitInfo.info.getAttributes();
+ }
+ if (withSizeInfo) {
+ Directory dir = segmentCommitInfo.info.dir;
+ List> files =
+ segmentCommitInfo.files().stream()
+ .map(
+ f -> {
+ long size = -1;
+ try {
+ size = dir.fileLength(f);
+ } catch (IOException e) {
+ }
+ return new Pair(f, size);
+ })
+ .sorted(
+ (p1, p2) -> {
+ if (p1.second() > p2.second()) {
+ return -1;
+ } else if (p1.second() < p2.second()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ })
+ .collect(Collectors.toList());
+ if (!files.isEmpty()) {
+ final var topFiles = new HashMap();
+ for (int i = 0; i < Math.min(files.size(), 5); i++) {
+ Pair p = files.get(i);
+ topFiles.put(p.first(), RamUsageEstimator.humanReadableUnits(p.second()));
+ }
+ segmentInfo.largestFilesByName = topFiles;
+ }
+ }
+
+ if (withFieldInfos) {
+ if (seg == null) {
+ log.debug(
+ "Skipping segment info - not available as a SegmentReader: {}", segmentCommitInfo);
+ } else {
+ FieldInfos fis = seg.getFieldInfos();
+ final var fields = new HashMap();
+ for (FieldInfo fi : fis) {
+ fields.put(fi.name, getFieldInfo(seg, fi, schema));
+ }
+ segmentInfo.fields = fields;
+ }
+ }
+
+ return segmentInfo;
+ }
+
+ private GetSegmentDataResponse.SegmentSingleFieldInfo getFieldInfo(
+ SegmentReader reader, FieldInfo fi, IndexSchema schema) {
+ final var responseFieldInfo = new GetSegmentDataResponse.SegmentSingleFieldInfo();
+ StringBuilder flags = new StringBuilder();
+ IndexOptions opts = fi.getIndexOptions();
+ flags.append((opts != IndexOptions.NONE) ? FieldFlag.INDEXED.getAbbreviation() : '-');
+ DocValuesType dvt = fi.getDocValuesType();
+ if (dvt != DocValuesType.NONE) {
+ flags.append(FieldFlag.DOC_VALUES.getAbbreviation());
+ switch (dvt) {
+ case NUMERIC:
+ flags.append("num");
+ break;
+ case BINARY:
+ flags.append("bin");
+ break;
+ case SORTED:
+ flags.append("srt");
+ break;
+ case SORTED_NUMERIC:
+ flags.append("srn");
+ break;
+ case SORTED_SET:
+ flags.append("srs");
+ break;
+ default:
+ flags.append("???"); // should not happen
+ }
+ } else {
+ flags.append("----");
+ }
+ flags.append((fi.hasVectors()) ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-');
+ flags.append((fi.omitsNorms()) ? FieldFlag.OMIT_NORMS.getAbbreviation() : '-');
+
+ flags.append((DOCS == opts) ? FieldFlag.OMIT_TF.getAbbreviation() : '-');
+
+ flags.append((DOCS_AND_FREQS == opts) ? FieldFlag.OMIT_POSITIONS.getAbbreviation() : '-');
+
+ flags.append(
+ (DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS == opts)
+ ? FieldFlag.STORE_OFFSETS_WITH_POSITIONS.getAbbreviation()
+ : '-');
+
+ flags.append((fi.hasPayloads() ? "p" : "-"));
+ flags.append((fi.isSoftDeletesField() ? "s" : "-"));
+ if (fi.getPointDimensionCount() > 0 || fi.getPointIndexDimensionCount() > 0) {
+ flags.append(":");
+ flags.append(fi.getPointDimensionCount()).append(':');
+ flags.append(fi.getPointIndexDimensionCount()).append(':');
+ flags.append(fi.getPointNumBytes());
+ }
+
+ responseFieldInfo.flags = flags.toString();
+
+ try {
+ Terms terms = reader.terms(fi.name);
+ if (terms != null) {
+ responseFieldInfo.docCount = terms.getDocCount();
+ responseFieldInfo.termCount = terms.size();
+ responseFieldInfo.sumDocFreq = terms.getSumDocFreq();
+ responseFieldInfo.sumTotalTermFreq = terms.getSumTotalTermFreq();
+ }
+ } catch (Exception e) {
+ log.debug("Exception retrieving term stats for field {}", fi.name, e);
+ }
+
+ // check compliance of the index with the current schema
+ SchemaField sf = schema.getFieldOrNull(fi.name);
+ boolean hasPoints = fi.getPointDimensionCount() > 0 || fi.getPointIndexDimensionCount() > 0;
+
+ if (sf != null) {
+ responseFieldInfo.schemaType = sf.getType().getTypeName();
+ final var nonCompliant = new HashMap();
+ if (sf.hasDocValues()
+ && fi.getDocValuesType() == DocValuesType.NONE
+ && fi.getIndexOptions() != IndexOptions.NONE) {
+ nonCompliant.put(
+ "docValues", "schema=" + sf.getType().getUninversionType(sf) + ", segment=false");
+ }
+ if (!sf.hasDocValues() && fi.getDocValuesType() != DocValuesType.NONE) {
+ nonCompliant.put("docValues", "schema=false, segment=" + fi.getDocValuesType().toString());
+ }
+ if (!sf.isPolyField()) { // difficult to find all sub-fields in a general way
+ if (sf.indexed() != ((fi.getIndexOptions() != IndexOptions.NONE) || hasPoints)) {
+ nonCompliant.put(
+ "indexed", "schema=" + sf.indexed() + ", segment=" + fi.getIndexOptions());
+ }
+ }
+ if (!hasPoints && (sf.omitNorms() != fi.omitsNorms())) {
+ nonCompliant.put("omitNorms", "schema=" + sf.omitNorms() + ", segment=" + fi.omitsNorms());
+ }
+ if (sf.storeTermVector() != fi.hasVectors()) {
+ nonCompliant.put(
+ "termVectors", "schema=" + sf.storeTermVector() + ", segment=" + fi.hasVectors());
+ }
+ if (sf.storeOffsetsWithPositions()
+ != (fi.getIndexOptions() == IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)) {
+ nonCompliant.put(
+ "storeOffsetsWithPositions",
+ "schema=" + sf.storeOffsetsWithPositions() + ", segment=" + fi.getIndexOptions());
+ }
+
+ if (nonCompliant.size() > 0) {
+ nonCompliant.put("schemaField", sf.toString());
+ responseFieldInfo.nonCompliant = nonCompliant;
+ }
+ } else {
+ responseFieldInfo.schemaType = "(UNKNOWN)";
+ }
+
+ return responseFieldInfo;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
index 9a96b34afc0..22f492abf92 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
@@ -91,6 +91,9 @@ public static void squashIntoNamedListWithoutHeader(
}
public static String getMediaTypeFromWtParam(SolrParams params, String defaultMediaType) {
+ if (params == null) {
+ return defaultMediaType;
+ }
final String wtParam = params.get(WT);
if (StrUtils.isBlank(wtParam)) return defaultMediaType;
diff --git a/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java b/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
index 1110880529c..3760bfc4590 100644
--- a/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
+++ b/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
@@ -63,6 +63,7 @@ public Response toResponse(Exception exception) {
// success/failure for AuditLogging, and other logic.
final SolrQueryResponse solrQueryResponse =
(SolrQueryResponse) containerRequestContext.getProperty(SOLR_QUERY_RESPONSE);
+
final SolrQueryRequest solrQueryRequest =
(SolrQueryRequest) containerRequestContext.getProperty(SOLR_QUERY_REQUEST);
if (exception instanceof WebApplicationException wae) {
diff --git a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
index e5a7f7150cc..44c08bff03e 100644
--- a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
@@ -63,9 +63,9 @@ public void filter(
final SolrQueryRequest solrQueryRequest =
(SolrQueryRequest) requestContext.getProperty(SOLR_QUERY_REQUEST);
- final String mediaType =
- V2ApiUtils.getMediaTypeFromWtParam(
- solrQueryRequest.getParams(), MediaType.APPLICATION_JSON);
+ // TODO Is it valid for SQRequest to be null?
+ final var params = (solrQueryRequest != null) ? solrQueryRequest.getParams() : null;
+ final String mediaType = V2ApiUtils.getMediaTypeFromWtParam(params, MediaType.APPLICATION_JSON);
if (mediaType != null) {
responseContext.getHeaders().putSingle(CONTENT_TYPE, mediaType);
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
index 0fe1b755d62..5239deeaeac 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
@@ -21,6 +21,9 @@
import static org.apache.solr.common.cloud.ZkStateReader.NUM_SHARDS_PROP;
import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
import static org.apache.solr.common.params.CollectionAdminParams.DEFAULTS;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
@@ -29,6 +32,7 @@
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -39,11 +43,14 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.lucene.tests.util.TestUtil;
+import org.apache.lucene.util.Version;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionsApi;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.CoreStatus;
import org.apache.solr.client.solrj.request.V2Request;
@@ -569,14 +576,7 @@ private void checkCollectionProperty(String collection, String propertyName, Str
fail("Timed out waiting for cluster property value");
}
- @Test
- public void testColStatus() throws Exception {
- String collectionName = getSaferTestName();
- CollectionAdminRequest.createCollection(collectionName, "conf2", 2, 2)
- .process(cluster.getSolrClient());
-
- cluster.waitForActiveCollection(collectionName, 2, 4);
-
+ private void indexSomeDocs(String collectionName) throws SolrServerException, IOException {
SolrClient client = cluster.getSolrClient();
byte[] binData = collectionName.getBytes(StandardCharsets.UTF_8);
// index some docs
@@ -602,13 +602,97 @@ public void testColStatus() throws Exception {
client.add(collectionName, doc);
}
client.commit(collectionName);
+ }
+
+ private void assertRspPathNull(SolrResponse rsp, String... pathSegments) {
+ assertNull(Utils.getObjectByPath(rsp.getResponse(), false, Arrays.asList(pathSegments)));
+ }
+
+ private void assertRspPathNotNull(SolrResponse rsp, String... pathSegments) {
+ assertNotNull(Utils.getObjectByPath(rsp.getResponse(), false, Arrays.asList(pathSegments)));
+ }
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testColStatus() throws Exception {
+ String collectionName = getSaferTestName();
+ CollectionAdminRequest.createCollection(collectionName, "conf2", 2, 2)
+ .process(cluster.getSolrClient());
+
+ cluster.waitForActiveCollection(collectionName, 2, 4);
+ indexSomeDocs(collectionName);
+
+ // Returns basic info if no additional flags are set
CollectionAdminRequest.ColStatus req = CollectionAdminRequest.collectionStatus(collectionName);
+ CollectionAdminResponse rsp = req.process(cluster.getSolrClient());
+ assertEquals(0, rsp.getStatus());
+ assertNotNull(rsp.getResponse().get(collectionName));
+ assertNotNull(rsp.getResponse().findRecursive(collectionName, "properties"));
+ final var collPropMap =
+ (Map) rsp.getResponse().findRecursive(collectionName, "properties");
+ assertEquals("conf2", collPropMap.get("configName"));
+ assertEquals(2L, collPropMap.get("nrtReplicas"));
+ assertEquals("0", collPropMap.get("tlogReplicas"));
+ assertEquals("0", collPropMap.get("pullReplicas"));
+ assertEquals(
+ 2, ((NamedList) rsp.getResponse().findRecursive(collectionName, "shards")).size());
+ assertNotNull(rsp.getResponse().findRecursive(collectionName, "shards", "shard1", "leader"));
+ // Ensure more advanced info is not returned
+ assertNull(
+ rsp.getResponse().findRecursive(collectionName, "shards", "shard1", "leader", "segInfos"));
+
+ // Returns segment metadata iff requested
+ req = CollectionAdminRequest.collectionStatus(collectionName);
+ req.setWithSegments(true);
+ rsp = req.process(cluster.getSolrClient());
+ assertEquals(0, rsp.getStatus());
+ assertNotNull(rsp.getResponse().get(collectionName));
+ assertRspPathNotNull(
+ rsp, collectionName, "shards", "shard1", "leader", "segInfos", "segments", "_0");
+ // Ensure field, size, etc. information isn't returned if only segment data was requested
+ assertRspPathNull(
+ rsp, collectionName, "shards", "shard1", "leader", "segInfos", "segments", "_0", "fields");
+ assertRspPathNull(
+ rsp,
+ collectionName,
+ "shards",
+ "shard1",
+ "leader",
+ "segInfos",
+ "segments",
+ "_0",
+ "largestFiles");
+
+ // Returns segment metadata and file-size info iff requested
+ // (Note that 'sizeInfo=true' should implicitly enable segments=true)
+ req = CollectionAdminRequest.collectionStatus(collectionName);
+ req.setWithSizeInfo(true);
+ rsp = req.process(cluster.getSolrClient());
+ assertEquals(0, rsp.getStatus());
+ assertRspPathNotNull(rsp, collectionName);
+ assertRspPathNotNull(
+ rsp, collectionName, "shards", "shard1", "leader", "segInfos", "segments", "_0");
+ assertRspPathNotNull(
+ rsp,
+ collectionName,
+ "shards",
+ "shard1",
+ "leader",
+ "segInfos",
+ "segments",
+ "_0",
+ "largestFiles");
+ // Ensure field, etc. information isn't returned if only segment+size data was requested
+ assertRspPathNull(
+ rsp, collectionName, "shards", "shard1", "leader", "segInfos", "segments", "_0", "fields");
+
+ // Set all flags and ensure everything is returned as expected
+ req = CollectionAdminRequest.collectionStatus(collectionName);
+ req.setWithSegments(true);
req.setWithFieldInfo(true);
req.setWithCoreInfo(true);
- req.setWithSegments(true);
req.setWithSizeInfo(true);
- CollectionAdminResponse rsp = req.process(cluster.getSolrClient());
+ rsp = req.process(cluster.getSolrClient());
assertEquals(0, rsp.getStatus());
@SuppressWarnings({"unchecked"})
List nonCompliant =
@@ -616,14 +700,22 @@ public void testColStatus() throws Exception {
assertEquals(nonCompliant.toString(), 1, nonCompliant.size());
assertTrue(nonCompliant.toString(), nonCompliant.contains("(NONE)"));
@SuppressWarnings({"unchecked"})
- NamedList segInfos =
- (NamedList)
- rsp.getResponse()
- .findRecursive(collectionName, "shards", "shard1", "leader", "segInfos");
- assertNotNull(Utils.toJSONString(rsp), segInfos.findRecursive("info", "core", "startTime"));
- assertNotNull(Utils.toJSONString(rsp), segInfos.get("fieldInfoLegend"));
+ final var segInfos =
+ (Map)
+ Utils.getObjectByPath(
+ rsp.getResponse(),
+ false,
+ List.of(collectionName, "shards", "shard1", "leader", "segInfos"));
assertNotNull(
- Utils.toJSONString(rsp), segInfos.findRecursive("segments", "_0", "fields", "id", "flags"));
+ Utils.toJSONString(rsp),
+ Utils.getObjectByPath(segInfos, false, List.of("info", "core", "startTime")));
+ assertNotNull(
+ Utils.toJSONString(rsp),
+ Utils.getObjectByPath(segInfos, false, List.of("fieldInfoLegend")));
+ assertNotNull(
+ Utils.toJSONString(rsp),
+ Utils.getObjectByPath(segInfos, false, List.of("segments", "_0", "fields", "id", "flags")));
+
// test for replicas not active - SOLR-13882
DocCollection coll = cluster.getSolrClient().getClusterState().getCollection(collectionName);
Replica firstReplica = coll.getSlice("shard1").getReplicas().iterator().next();
@@ -637,7 +729,10 @@ public void testColStatus() throws Exception {
assertEquals(0, rsp.getStatus());
Number down =
(Number)
- rsp.getResponse().findRecursive(collectionName, "shards", "shard1", "replicas", "down");
+ Utils.getObjectByPath(
+ rsp.getResponse(),
+ false,
+ List.of(collectionName, "shards", "shard1", "replicas", "down"));
assertTrue(
"should be some down replicas, but there were none in shard1:" + rsp, down.intValue() > 0);
@@ -652,10 +747,8 @@ public void testColStatus() throws Exception {
req = CollectionAdminRequest.collectionStatus(implicitColl);
rsp = req.process(cluster.getSolrClient());
assertNotNull(rsp.getResponse().get(implicitColl));
- assertNotNull(
- rsp.toString(), rsp.getResponse().findRecursive(implicitColl, "shards", "shardA"));
- assertNotNull(
- rsp.toString(), rsp.getResponse().findRecursive(implicitColl, "shards", "shardB"));
+ assertRspPathNotNull(rsp, implicitColl, "shards", "shardA");
+ assertRspPathNotNull(rsp, implicitColl, "shards", "shardB");
}
@Test
@@ -697,6 +790,69 @@ public void testColStatusCollectionName() throws Exception {
assertNotNull(rsp.getResponse().get(collectionNames[0]));
}
+ /**
+ * Unit test for the v2 API: GET /api/collections/$collName
+ *
+ * Uses the OAS-generated SolrRequest/SolrResponse API binding.
+ */
+ @Test
+ public void testV2BasicCollectionStatus() throws Exception {
+ final String simpleCollName = "simpleCollection";
+ CollectionAdminRequest.createCollection(simpleCollName, "conf2", 2, 1, 1, 1)
+ .process(cluster.getSolrClient());
+ cluster.waitForActiveCollection(simpleCollName, 2, 6);
+ indexSomeDocs(simpleCollName);
+
+ final var simpleResponse =
+ new CollectionsApi.GetCollectionStatus(simpleCollName)
+ .process(cluster.getSolrClient())
+ .getParsed();
+ assertEquals(simpleCollName, simpleResponse.name);
+ assertEquals(2, simpleResponse.shards.size());
+ assertEquals(Integer.valueOf(2), simpleResponse.activeShards);
+ assertEquals(Integer.valueOf(0), simpleResponse.inactiveShards);
+ assertEquals(Integer.valueOf(1), simpleResponse.properties.nrtReplicas);
+ assertEquals(Integer.valueOf(1), simpleResponse.properties.replicationFactor);
+ assertEquals(Integer.valueOf(1), simpleResponse.properties.pullReplicas);
+ assertEquals(Integer.valueOf(1), simpleResponse.properties.tlogReplicas);
+ assertNotNull(simpleResponse.shards.get("shard1").leader);
+ assertNull(simpleResponse.shards.get("shard1").leader.segInfos);
+
+ // Ensure segment data present when request sets 'segments=true' flag
+ final var segmentDataRequest = new CollectionsApi.GetCollectionStatus(simpleCollName);
+ segmentDataRequest.setSegments(true);
+ final var segmentDataResponse = segmentDataRequest.process(cluster.getSolrClient()).getParsed();
+ var segmentData = segmentDataResponse.shards.get("shard1").leader.segInfos;
+ assertNotNull(segmentData);
+ assertTrue(segmentData.info.numSegments > 0); // Expect at least one segment
+ assertEquals(segmentData.info.numSegments.intValue(), segmentData.segments.size());
+ assertEquals(Version.LATEST.toString(), segmentData.info.commitLuceneVersion);
+ // Ensure field, size, etc. data not provided
+ assertNull(segmentData.segments.get("_0").fields);
+ assertNull(segmentData.segments.get("_0").largestFilesByName);
+
+ // Ensure file-size data present when request sets sizeInfo flag
+ final var segmentFileSizeRequest = new CollectionsApi.GetCollectionStatus(simpleCollName);
+ segmentFileSizeRequest.setSizeInfo(true);
+ final var segmentFileSizeResponse =
+ segmentFileSizeRequest.process(cluster.getSolrClient()).getParsed();
+ segmentData = segmentFileSizeResponse.shards.get("shard1").leader.segInfos;
+ assertNotNull(segmentData);
+ final var largeFileList = segmentData.segments.get("_0").largestFilesByName;
+ assertNotNull(largeFileList);
+ // Hard to assert what the largest index files should be, but:
+ // - there should be at least 1 entry and...
+ // - all keys/values should be non-empty
+ assertTrue(largeFileList.size() > 0);
+ largeFileList.forEach(
+ (fileName, size) -> {
+ assertThat(fileName, is(not(emptyString())));
+ assertThat(size, is(not(emptyString())));
+ });
+ // Ensure field, etc. data not provided
+ assertNull(segmentData.segments.get("_0").fields);
+ }
+
private static final int NUM_DOCS = 10;
@Test
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/IndexSizeEstimatorTest.java b/solr/core/src/test/org/apache/solr/handler/admin/IndexSizeEstimatorTest.java
index 5523ad23c15..54aa6394902 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/IndexSizeEstimatorTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/IndexSizeEstimatorTest.java
@@ -19,6 +19,7 @@
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -32,6 +33,7 @@
import org.apache.lucene.tests.util.TestUtil;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.solr.client.api.model.CollectionStatusResponse;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -39,10 +41,11 @@
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.common.util.Utils;
import org.apache.solr.core.SolrCore;
import org.apache.solr.embedded.JettySolrRunner;
+import org.apache.solr.jersey.SolrJacksonMapper;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.util.RefCounted;
import org.apache.solr.util.TimeOut;
@@ -177,56 +180,42 @@ public void testIntegration() throws Exception {
assertEquals(0, sampledRsp.getStatus());
for (int i : Arrays.asList(1, 2)) {
@SuppressWarnings({"unchecked"})
- NamedList segInfos =
- (NamedList)
- rsp.getResponse()
- .findRecursive(collection, "shards", "shard" + i, "leader", "segInfos");
- @SuppressWarnings({"unchecked"})
- NamedList rawSize = (NamedList) segInfos.get("rawSize");
+ final var segInfosRaw =
+ Utils.getObjectByPath(
+ rsp.getResponse(),
+ false,
+ List.of(collection, "shards", "shard" + i, "leader", "segInfos"));
+ final var segInfos =
+ SolrJacksonMapper.getObjectMapper()
+ .convertValue(segInfosRaw, CollectionStatusResponse.SegmentInfo.class);
+
+ final var rawSize = segInfos.rawSize;
assertNotNull("rawSize missing", rawSize);
- @SuppressWarnings({"unchecked"})
- Map rawSizeMap = rawSize.asMap(10);
- @SuppressWarnings({"unchecked"})
- Map fieldsBySize =
- (Map) rawSizeMap.get(IndexSizeEstimator.FIELDS_BY_SIZE);
+ Map fieldsBySize = rawSize.fieldsBySize;
assertNotNull("fieldsBySize missing", fieldsBySize);
assertEquals(fieldsBySize.toString(), fields.size(), fieldsBySize.size());
fields.forEach(field -> assertNotNull("missing field " + field, fieldsBySize.get(field)));
- @SuppressWarnings({"unchecked"})
- Map typesBySize =
- (Map) rawSizeMap.get(IndexSizeEstimator.TYPES_BY_SIZE);
+ Map typesBySize = rawSize.typesBySize;
assertNotNull("typesBySize missing", typesBySize);
assertTrue("expected at least 8 types: " + typesBySize, typesBySize.size() >= 8);
- @SuppressWarnings({"unchecked"})
- Map summary =
- (Map) rawSizeMap.get(IndexSizeEstimator.SUMMARY);
+ Map summary = rawSize.summary;
assertNotNull("summary missing", summary);
assertEquals(summary.toString(), fields.size(), summary.size());
fields.forEach(field -> assertNotNull("missing field " + field, summary.get(field)));
@SuppressWarnings({"unchecked"})
- Map details =
- (Map) rawSizeMap.get(IndexSizeEstimator.DETAILS);
+ Map details = (Map) rawSize.details;
assertNotNull("details missing", summary);
assertEquals(details.keySet().toString(), 6, details.size());
// compare with sampled
- @SuppressWarnings({"unchecked"})
- NamedList sampledRawSize =
- (NamedList)
- rsp.getResponse()
- .findRecursive(
- collection, "shards", "shard" + i, "leader", "segInfos", "rawSize");
+ final var sampledRawSize = rawSize;
assertNotNull("sampled rawSize missing", sampledRawSize);
- @SuppressWarnings({"unchecked"})
- Map sampledRawSizeMap = rawSize.asMap(10);
- @SuppressWarnings({"unchecked"})
- Map sampledFieldsBySize =
- (Map) sampledRawSizeMap.get(IndexSizeEstimator.FIELDS_BY_SIZE);
+ Map sampledFieldsBySize = sampledRawSize.fieldsBySize;
assertNotNull("sampled fieldsBySize missing", sampledFieldsBySize);
fieldsBySize.forEach(
(k, v) -> {
- double size = fromHumanReadableUnits((String) v);
- double sampledSize = fromHumanReadableUnits((String) sampledFieldsBySize.get(k));
+ double size = fromHumanReadableUnits(v);
+ double sampledSize = fromHumanReadableUnits(sampledFieldsBySize.get(k));
double delta = size * 0.5;
assertEquals("sampled size of " + k + " is wildly off", size, sampledSize, delta);
});
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
index 265968b23c8..8600ed8236b 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestApiFramework.java
@@ -114,7 +114,6 @@ public void testFramework() {
methodNames.add(rsp.getValues()._getStr("/spec[1]/methods[0]", null));
methodNames.add(rsp.getValues()._getStr("/spec[2]/methods[0]", null));
assertTrue(methodNames.contains("POST"));
- assertTrue(methodNames.contains("GET"));
methodNames = new HashSet<>();
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
index 98d000773c7..1a61b6516fd 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionAPIMappingTest.java
@@ -21,9 +21,7 @@
import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonParams.ACTION;
-import static org.apache.solr.common.params.CoreAdminParams.SHARD;
-import java.util.Map;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.SolrParams;
@@ -56,7 +54,6 @@ public void populateApiBag() {
apiBag.registerObject(new ModifyCollectionAPI(collectionsHandler));
apiBag.registerObject(new MoveReplicaAPI(collectionsHandler));
apiBag.registerObject(new RebalanceLeadersAPI(collectionsHandler));
- apiBag.registerObject(new CollectionStatusAPI(collectionsHandler));
}
@Override
@@ -69,17 +66,6 @@ public boolean isCoreSpecific() {
return false;
}
- @Test
- public void testGetCollectionStatus() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params(
- "/collections/collName", "GET", Map.of(SHARD, new String[] {"shard2"}));
-
- assertEquals(CollectionParams.CollectionAction.CLUSTERSTATUS.toString(), v1Params.get(ACTION));
- assertEquals("collName", v1Params.get(COLLECTION));
- assertEquals("shard2", v1Params.get(SHARD));
- }
-
@Test
public void testModifyCollectionAllProperties() throws Exception {
final SolrParams v1Params =
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
index f4811158ef7..6f89932a3a6 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
@@ -1050,11 +1050,9 @@ http://localhost:8983/solr/admin/collections?action=COLSTATUS&collection=techpro
V2 API::
+
====
-The closest V2 API is this one, but doesn't support all the features of the V1 equivalent.
-
[source,bash]
----
-curl -X GET http://localhost:8983/api/collections/techproducts_v2
+curl -X GET "http://localhost:8983/api/collections/techproducts_v2?coreInfo=true&segments=true&fieldInfo=true&sizeInfo=true"
----
====
======
@@ -1072,7 +1070,8 @@ Such incompatibilities may result from incompatible schema changes or after migr
|===
+
Collection name.
-If missing then it means all collections.
+Provided as a query-parameter in v1 requests, and as a path-parameter in v2.
+If missing then information is returned about all collections (supported by v1 requests only).
`coreInfo`::
+