diff --git a/changelog/unreleased/SOLR-18144.yml b/changelog/unreleased/SOLR-18144.yml new file mode 100644 index 000000000000..18c025dfcebe --- /dev/null +++ b/changelog/unreleased/SOLR-18144.yml @@ -0,0 +1,7 @@ +title: Fixed schema designer to create missing .system collection. +type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other +authors: + - name: Eric Pugh +links: + - name: SOLR-18144 + url: https://issues.apache.org/jira/browse/SOLR-18144 diff --git a/solr/core/src/java/org/apache/solr/handler/designer/SchemaDesignerConfigSetHelper.java b/solr/core/src/java/org/apache/solr/handler/designer/SchemaDesignerConfigSetHelper.java index f24fe26ed61c..2f1d8a497308 100644 --- a/solr/core/src/java/org/apache/solr/handler/designer/SchemaDesignerConfigSetHelper.java +++ b/solr/core/src/java/org/apache/solr/handler/designer/SchemaDesignerConfigSetHelper.java @@ -474,6 +474,7 @@ protected void validateTypeChange(String configSet, SchemaField field, FieldType void deleteStoredSampleDocs(String configSet) { try { + ensureSystemCollectionExists(); cloudClient().deleteByQuery(BLOB_STORE_ID, "id:" + configSet + "_sample/*", 10); } catch (IOException | SolrServerException | SolrException exc) { final String excStr = exc.toString(); @@ -481,8 +482,47 @@ void deleteStoredSampleDocs(String configSet) { } } + private void ensureSystemCollectionExists() throws IOException, SolrServerException { + if (!zkStateReader().getClusterState().hasCollection(BLOB_STORE_ID)) { + log.info("Creating {} collection for blob storage", BLOB_STORE_ID); + try { + int liveNodes = zkStateReader().getClusterState().getLiveNodes().size(); + int replicationFactor = Math.max(1, Math.min(3, liveNodes)); + CollectionAdminRequest.createCollection(BLOB_STORE_ID, null, 1, replicationFactor) + .process(cloudClient()); + } catch (SolrServerException | IOException e) { + // Handle race where another node created the collection between the hasCollection() + // check above and the createCollection() call. If the collection now exists, treat + // this as success; otherwise, propagate the original failure. + if (zkStateReader().getClusterState().hasCollection(BLOB_STORE_ID)) { + if (log.isInfoEnabled()) { + log.info( + "Collection {} already exists after failed create attempt; treating as success", + BLOB_STORE_ID); + } + return; + } + throw e; + } + try { + zkStateReader().waitForState(BLOB_STORE_ID, 30, TimeUnit.SECONDS, Objects::nonNull); + } catch (InterruptedException | TimeoutException e) { + throw new IOException( + "Failed to see created collection " + BLOB_STORE_ID + " reflected in cluster state", + SolrZkClient.checkInterrupted(e)); + } + } + } + @SuppressWarnings("unchecked") List getStoredSampleDocs(final String configSet) throws IOException { + + try { + ensureSystemCollectionExists(); + } catch (SolrServerException e) { + throw new IOException("Failed to ensure .system collection exists", e); + } + var request = new GenericSolrRequest(SolrRequest.METHOD.GET, "/blob/" + configSet + "_sample"); request.setRequiresCollection(true); request.setResponseParser(new InputStreamResponseParser("filestream")); @@ -496,12 +536,27 @@ List getStoredSampleDocs(final String configSet) throws IOExc } else return Collections.emptyList(); } catch (SolrServerException e) { throw new IOException("Failed to lookup stored docs for " + configSet + " due to: " + e); + } catch (SolrException e) { + // Collection not found or blob not found - treat as no documents stored + if (e.code() == ErrorCode.NOT_FOUND.code) { + if (log.isDebugEnabled()) { + log.debug("No stored sample docs found for {}", configSet, e); + } + return Collections.emptyList(); + } + // For other SolrExceptions, propagate as an IOException to avoid hiding real server problems + throw new IOException("Failed to lookup stored docs for " + configSet, e); } finally { IOUtils.closeQuietly(inputStream); } } void storeSampleDocs(final String configSet, List docs) throws IOException { + try { + ensureSystemCollectionExists(); + } catch (SolrServerException e) { + throw new IOException("Failed to ensure .system collection exists", e); + } docs.forEach(d -> d.removeField(VERSION_FIELD)); // remove _version_ field before storing ... postDataToBlobStore( cloudClient(), diff --git a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerAPI.java b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerAPI.java index 893ad0f16ecf..f682951d6736 100644 --- a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerAPI.java +++ b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerAPI.java @@ -33,7 +33,6 @@ import java.util.Map; import java.util.Optional; import org.apache.solr.client.solrj.SolrQuery; -import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.common.SolrDocumentList; @@ -70,9 +69,6 @@ public static void createCluster() throws Exception { configureCluster(1) .addConfig(DEFAULT_CONFIGSET_NAME, new File(ExternalPaths.DEFAULT_CONFIGSET).toPath()) .configure(); - // SchemaDesignerAPI depends on the blob store - CollectionAdminRequest.createCollection(BLOB_STORE_ID, 1, 1).process(cluster.getSolrClient()); - cluster.waitForActiveCollection(BLOB_STORE_ID, 1, 1); } @AfterClass diff --git a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerConfigSetHelper.java b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerConfigSetHelper.java index 861261c99049..3f666039fa1b 100644 --- a/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerConfigSetHelper.java +++ b/solr/core/src/test/org/apache/solr/handler/designer/TestSchemaDesignerConfigSetHelper.java @@ -32,7 +32,6 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.util.SimpleOrderedMap; @@ -60,9 +59,6 @@ public static void createCluster() throws Exception { configureCluster(1) .addConfig(DEFAULT_CONFIGSET_NAME, new File(ExternalPaths.DEFAULT_CONFIGSET).toPath()) .configure(); - // SchemaDesignerConfigSetHelper depends on the blob store - CollectionAdminRequest.createCollection(BLOB_STORE_ID, 1, 1).process(cluster.getSolrClient()); - cluster.waitForActiveCollection(BLOB_STORE_ID, 1, 1); } @AfterClass