Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOLR-16699: Expose creationTimeMillis in COLSTATUS API #2226

Merged
merged 5 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ Improvements
* SOLR-17119: When registering or updating a ConfigurablePlugin through the `/cluster/plugin` API,
config validation exceptions are now propagated to the callers. (Yohann Callea)

* SOLR-16699: Add Collection creation time to the Collection Status API response (Julien Pilourdault, Paul McArthur, David Smiley)
dsmiley marked this conversation as resolved.
Show resolved Hide resolved

Optimizations
---------------------
(No changes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MODIFYCOLLECTION;

import java.lang.invoke.MethodHandles;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
Expand Down Expand Up @@ -636,7 +637,8 @@ private ClusterState fetchStateForCollection() throws KeeperException, Interrupt
data,
Collections.emptySet(),
updater.getCollectionName(),
zkStateReader.getZkClient());
zkStateReader.getZkClient(),
Instant.ofEpochMilli(stat.getCtime()));

return clusterState;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.apache.solr.common.params.CommonParams.NAME;

import java.lang.invoke.MethodHandles;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -133,9 +134,18 @@ public ZkWriteCommand createCollection(ClusterState clusterState, ZkNodeProps me
}

assert !collectionProps.containsKey(CollectionAdminParams.COLL_CONF);

// This instance does not fully capture what will be persisted: the zkNodeVersion and
// creationTime will only be definitively set in ZK. Hence, the defaults passed here.
DocCollection newCollection =
DocCollection.create(
cName, slices, collectionProps, router, -1, stateManager.getPrsSupplier(cName));
cName,
slices,
collectionProps,
router,
-1,
Instant.EPOCH,
stateManager.getPrsSupplier(cName));

return new ZkWriteCommand(cName, newCollection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public ZkWriteCommand modifyCollection(final ClusterState clusterState, ZkNodePr
props,
coll.getRouter(),
coll.getZNodeVersion(),
coll.getCreationTime(),
stateManager.getPrsSupplier(coll.getName()));
if (replicaOps == null) {
return new ZkWriteCommand(coll.getName(), collection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.codahale.metrics.Timer;
import java.lang.invoke.MethodHandles;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -303,18 +304,21 @@ public ClusterState writePendingUpdates(
c.getProperties(),
c.getRouter(),
stat.getVersion(),
Instant.ofEpochMilli(stat.getCtime()),
PerReplicaStatesOps.getZkClientPrsSupplier(reader.getZkClient(), path));
clusterState = clusterState.copyWith(name, newCollection);
} else {
log.debug("going to create_collection {}", path);
reader.getZkClient().create(path, data, CreateMode.PERSISTENT, true);
Stat stat = new Stat();
reader.getZkClient().create(path, data, CreateMode.PERSISTENT, true, stat);
DocCollection newCollection =
DocCollection.create(
name,
c.getSlicesMap(),
c.getProperties(),
c.getRouter(),
0,
Instant.ofEpochMilli(stat.getCtime()),
PerReplicaStatesOps.getZkClientPrsSupplier(reader.getZkClient(), path));
clusterState = clusterState.copyWith(name, newCollection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -221,7 +222,10 @@ public DocCollection readCollectionState(String collectionName) throws IOExcepti
repository.openInput(zkStateDir, COLLECTION_PROPS_FILE, IOContext.DEFAULT)) {
byte[] arr = new byte[(int) is.length()]; // probably ok since the json file should be small.
is.readBytes(arr, 0, (int) is.length());
ClusterState c_state = ClusterState.createFromJson(-1, arr, Collections.emptySet(), null);
// set a default created date, we don't aim at reading actual zookeeper state. The restored
// collection will have a new creation date when persisted in zookeeper.
ClusterState c_state =
ClusterState.createFromJson(-1, arr, Collections.emptySet(), Instant.EPOCH, null);
return c_state.getCollection(collectionName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ public void getClusterStatus(NamedList<Object> results)
collectionStatus = getCollectionStatus(docCollection, name, requestedShards);

collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
collectionStatus.put(
"creationTimeMillis", clusterStateCollection.getCreationTime().toEpochMilli());

if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
collectionStatus.put("aliases", collectionVsAliases.get(name));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public void getColStatus(NamedList<Object> results) {
}
SimpleOrderedMap<Object> colMap = new SimpleOrderedMap<>();
colMap.add("znodeVersion", coll.getZNodeVersion());
colMap.add("creationTimeMillis", coll.getCreationTime().toEpochMilli());
Map<String, Object> props = new TreeMap<>(coll.getProperties());
props.remove("shards");
colMap.add("properties", props);
Expand Down
20 changes: 15 additions & 5 deletions solr/core/src/test/org/apache/solr/cloud/ClusterStateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.solr.cloud;

import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand Down Expand Up @@ -60,17 +61,21 @@ public void testStoreAndRead() {
slices.put("shard2", slice2);
collectionStates.put(
"collection1",
DocCollection.create("collection1", slices, props, DocRouter.DEFAULT, 0, null));
DocCollection.create(
"collection1", slices, props, DocRouter.DEFAULT, 0, Instant.EPOCH, null));
collectionStates.put(
"collection2",
DocCollection.create("collection2", slices, props, DocRouter.DEFAULT, 0, null));
DocCollection.create(
"collection2", slices, props, DocRouter.DEFAULT, 0, Instant.EPOCH, null));

ClusterState clusterState = new ClusterState(liveNodes, collectionStates);
assertFalse(clusterState.getCollection("collection1").getProperties().containsKey("shards"));

byte[] bytes = Utils.toJSON(clusterState);

ClusterState loadedClusterState = ClusterState.createFromJson(-1, bytes, liveNodes, null);
Instant creationTime = Instant.now();
ClusterState loadedClusterState =
ClusterState.createFromJson(-1, bytes, liveNodes, creationTime, null);
assertFalse(
loadedClusterState.getCollection("collection1").getProperties().containsKey("shards"));

Expand All @@ -96,13 +101,18 @@ public void testStoreAndRead() {
.get("node1")
.getStr("prop2"));

loadedClusterState = ClusterState.createFromJson(-1, new byte[0], liveNodes, null);
assertEquals(creationTime, loadedClusterState.getCollection("collection1").getCreationTime());
assertEquals(creationTime, loadedClusterState.getCollection("collection2").getCreationTime());

loadedClusterState =
ClusterState.createFromJson(-1, new byte[0], liveNodes, Instant.now(), null);

assertEquals(
"Provided liveNodes not used properly", 2, loadedClusterState.getLiveNodes().size());
assertEquals("Should not have collections", 0, loadedClusterState.getCollectionsMap().size());

loadedClusterState = ClusterState.createFromJson(-1, (byte[]) null, liveNodes, null);
loadedClusterState =
ClusterState.createFromJson(-1, (byte[]) null, liveNodes, Instant.now(), null);

assertEquals(
"Provided liveNodes not used properly", 2, loadedClusterState.getLiveNodes().size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
Expand Down Expand Up @@ -1209,4 +1210,34 @@ public void testModifyCollectionAttribute() throws IOException, SolrServerExcept
.unsetAttribute("non_existent_attr")
.process(cluster.getSolrClient()));
}

@Test
public void testCollectionCreationTime() throws SolrServerException, IOException {
Instant beforeCreation = Instant.now();

String collectionName = getSaferTestName();
CollectionAdminRequest.createCollection(collectionName, "conf", 1, 1)
.setPerReplicaState(SolrCloudTestCase.USE_PER_REPLICA_STATE)
.process(cluster.getSolrClient());

cluster.waitForActiveCollection(collectionName, 1, 1);

Instant afterCreation = Instant.now();

CollectionAdminRequest.ColStatus req = CollectionAdminRequest.collectionStatus(collectionName);
CollectionAdminResponse response = req.process(cluster.getSolrClient());
assertEquals(0, response.getStatus());

NamedList<?> colStatus = (NamedList<?>) response.getResponse().get(collectionName);
Long creationTimeMillis = (Long) colStatus._get("creationTimeMillis", null);
assertNotNull("creationTimeMillis was not included in COLSTATUS response", creationTimeMillis);

Instant creationTime = Instant.ofEpochMilli(creationTimeMillis);
assertTrue(
"COLSTATUS creationTimeMillis should be after the test started",
creationTime.isAfter(beforeCreation));
assertTrue(
"COLSTATUS creationTimeMillis should not be after the collection creation was completed",
creationTime.isBefore(afterCreation));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.mockito.Mockito.when;

import java.lang.invoke.MethodHandles;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -688,6 +689,7 @@ private void handleCreateCollMessageProps(ZkNodeProps props) {
props.getProperties(),
DocRouter.DEFAULT,
0,
Instant.EPOCH,
distribStateManagerMock.getPrsSupplier(collName))));
}
if (CollectionParams.CollectionAction.ADDREPLICA.isEqual(props.getStr("operation"))) {
Expand Down
4 changes: 3 additions & 1 deletion solr/core/src/test/org/apache/solr/cloud/SliceStateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.solr.cloud;

import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand Down Expand Up @@ -59,7 +60,8 @@ public void testDefaultSliceState() {

ClusterState clusterState = new ClusterState(liveNodes, collectionStates);
byte[] bytes = Utils.toJSON(clusterState);
ClusterState loadedClusterState = ClusterState.createFromJson(-1, bytes, liveNodes, null);
ClusterState loadedClusterState =
ClusterState.createFromJson(-1, bytes, liveNodes, Instant.now(), null);

assertSame(
"Default state not set to active",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.solr.cloud.api.collections;

import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
Expand Down Expand Up @@ -123,7 +124,11 @@ public void testPropertiesOfReplica() throws Exception {

DocCollection c =
ClusterState.createFromCollectionMap(
0, (Map<String, Object>) Utils.fromJSON(node.data), Collections.emptySet(), null)
0,
(Map<String, Object>) Utils.fromJSON(node.data),
Collections.emptySet(),
Instant.EPOCH,
null)
.getCollection(collectionName);

Set<String> knownKeys =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.apache.solr.cloud.api.collections;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -491,7 +492,10 @@ private void clusterStatusWithCollection() throws IOException, SolrServerExcepti
Map<String, Object> collection = (Map<String, Object>) collections.get(COLLECTION_NAME);
assertNotNull(collection);
assertEquals("conf1", collection.get("configName"));
// assertEquals("1", collection.get("nrtReplicas"));

Instant creationTime = Instant.ofEpochMilli((long) collection.get("creationTimeMillis"));
assertEquals(
creationTime, client.getClusterState().getCollection(COLLECTION_NAME).getCreationTime());
}
}

Expand Down
Loading
Loading