From 3ea964b6e2303d88f8b58089f4f124c1d3b00f3d Mon Sep 17 00:00:00 2001 From: Austin DeNoble Date: Tue, 13 Feb 2024 20:53:18 -0500 Subject: [PATCH] add collections calls to PineconeControlPlaneClient, add new integration tests for collections, add new helpers for new collections tests --- build.gradle | 64 +++++++ .../java/io/pinecone/helpers/AssertRetry.java | 15 +- .../io/pinecone/helpers/IndexManager.java | 69 +++++++ .../controlPlane/pod/CollectionErrorTest.java | 143 ++++++++++++++ .../controlPlane/pod/CollectionTest.java | 180 ++++++++++++++++++ .../{index => }/pod/ConfigureIndexTest.java | 2 +- .../CreateDescribeListAndDeleteIndexTest.java | 2 +- .../CreateDescribeListAndDeleteIndexTest.java | 2 +- .../pinecone/PineconeControlPlaneClient.java | 56 +++++- .../pinecone/exceptions/HttpErrorMapper.java | 2 +- 10 files changed, 515 insertions(+), 20 deletions(-) create mode 100644 src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionErrorTest.java create mode 100644 src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionTest.java rename src/integration/java/io/pinecone/integration/controlPlane/{index => }/pod/ConfigureIndexTest.java (99%) rename src/integration/java/io/pinecone/integration/controlPlane/{index => }/pod/CreateDescribeListAndDeleteIndexTest.java (97%) rename src/integration/java/io/pinecone/integration/controlPlane/{index => }/serverless/CreateDescribeListAndDeleteIndexTest.java (96%) diff --git a/build.gradle b/build.gradle index dd01b849..aea0cca5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat; +import org.gradle.api.tasks.testing.logging.TestLogEvent; + plugins { id 'com.github.johnrengelman.shadow' version '6.1.0' id 'java-library' @@ -93,6 +96,36 @@ tasks.named('build') { test { useJUnitPlatform() + + + testLogging { + events = [TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT] + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + + debugOptions { + events = [ + TestLogEvent.STARTED, + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT + ] + exceptionFormat = TestExceptionFormat.FULL + } + + afterSuite { desc, result -> + if (!desc.parent) { + def output = "Results: ${result.resultType} (${result.testCount}) tests, ${result.successfulTestCount} successful, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped." + def startItem = "| ", endItem = " |" + def repeatLength = startItem.length() + output.length() + endItem.length() + println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) + } + } + } } task integrationTest(type: Test) { @@ -100,6 +133,37 @@ task integrationTest(type: Test) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath outputs.upToDateWhen { false } + + + testLogging { + events = [TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT] + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + + debugOptions { + events = [ + TestLogEvent.STARTED, + TestLogEvent.FAILED, + TestLogEvent.PASSED, + TestLogEvent.SKIPPED, + TestLogEvent.STANDARD_ERROR, + TestLogEvent.STANDARD_OUT + ] + exceptionFormat = TestExceptionFormat.FULL + + } + + afterSuite { desc, result -> + if (!desc.parent) { + def output = "Results: ${result.resultType} (${result.testCount}) tests, ${result.successfulTestCount} successful, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped." + def startItem = "| ", endItem = " |" + def repeatLength = startItem.length() + output.length() + endItem.length() + println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) + } + } + } } // Configure Auto Relocation diff --git a/src/integration/java/io/pinecone/helpers/AssertRetry.java b/src/integration/java/io/pinecone/helpers/AssertRetry.java index 1212893f..16f4c6cf 100644 --- a/src/integration/java/io/pinecone/helpers/AssertRetry.java +++ b/src/integration/java/io/pinecone/helpers/AssertRetry.java @@ -1,18 +1,21 @@ package io.pinecone.helpers; +import io.pinecone.exceptions.PineconeException; + import java.io.IOException; import java.util.concurrent.ExecutionException; public class AssertRetry { private static final int maxRetry = 4; - private static int delay = 1500; + private static final int delay = 1500; - public static void assertWithRetry(AssertionRunnable assertionRunnable) throws InterruptedException { + public static void assertWithRetry(AssertionRunnable assertionRunnable) throws InterruptedException, PineconeException { assertWithRetry(assertionRunnable, 2); } - public static void assertWithRetry(AssertionRunnable assertionRunnable, int backOff) throws InterruptedException { + public static void assertWithRetry(AssertionRunnable assertionRunnable, int backOff) throws InterruptedException, PineconeException { int retryCount = 0; + int delayCount = delay; boolean success = false; while (retryCount < maxRetry && !success) { @@ -21,14 +24,14 @@ public static void assertWithRetry(AssertionRunnable assertionRunnable, int back success = true; } catch (AssertionError | ExecutionException | IOException e) { retryCount++; - delay*=backOff; - Thread.sleep(delay); + Thread.sleep(delayCount); + delayCount*=backOff; } } } @FunctionalInterface public interface AssertionRunnable { - void run() throws AssertionError, ExecutionException, InterruptedException, IOException; + void run() throws AssertionError, ExecutionException, InterruptedException, IOException, PineconeException; } } diff --git a/src/integration/java/io/pinecone/helpers/IndexManager.java b/src/integration/java/io/pinecone/helpers/IndexManager.java index c99818a9..577b2688 100644 --- a/src/integration/java/io/pinecone/helpers/IndexManager.java +++ b/src/integration/java/io/pinecone/helpers/IndexManager.java @@ -1,12 +1,15 @@ package io.pinecone.helpers; import io.pinecone.*; +import io.pinecone.exceptions.PineconeException; import org.openapitools.client.model.*; import java.io.IOException; import java.util.List; import static io.pinecone.helpers.AssertRetry.assertWithRetry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; public class IndexManager { private static PineconeClientConfig config; @@ -79,6 +82,72 @@ private static String createNewIndex(PineconeControlPlaneClient controlPlaneClie return indexName; } + public static IndexModel waitUntilIndexIsReady(PineconeControlPlaneClient controlPlaneClient, String indexName, Integer totalMsToWait) throws InterruptedException { + IndexModel index = controlPlaneClient.describeIndex(indexName); + int waitedTimeMs = 0; + int intervalMs = 1500; + + while (!index.getStatus().getReady()) { + index = controlPlaneClient.describeIndex(indexName); + if (waitedTimeMs >= totalMsToWait) { + System.out.println("Index " + indexName + " not ready after " + waitedTimeMs + "ms"); + break; + } + if (index.getStatus().getReady()) { + Thread.sleep(3000); + System.out.println("Index " + indexName + " is ready after " + waitedTimeMs + 3000 + "ms"); + break; + } + Thread.sleep(intervalMs); + waitedTimeMs += intervalMs; + } + return index; + } + + public static IndexModel waitUntilIndexIsReady(PineconeControlPlaneClient controlPlaneClient, String indexName) throws InterruptedException { + return waitUntilIndexIsReady(controlPlaneClient, indexName, 120000); + } + + public static PineconeConnection createNewIndexAndConnect(PineconeControlPlaneClient controlPlaneClient, String indexName, int dimension, IndexMetric metric, CreateIndexRequestSpec spec) throws InterruptedException, PineconeException { + CreateIndexRequest createIndexRequest = new CreateIndexRequest().name(indexName).dimension(dimension).metric(metric).spec(spec); + controlPlaneClient.createIndex(createIndexRequest); + + // Wait until index is ready + waitUntilIndexIsReady(controlPlaneClient, indexName, 200000); + String host = controlPlaneClient.describeIndex(indexName).getHost(); + + PineconeClientConfig specificConfig = new PineconeClientConfig().withApiKey(System.getenv("PINECONE_API_KEY")); + PineconeClient dataPlaneClient = new PineconeClient(specificConfig); + + return dataPlaneClient.connect( + new PineconeConnectionConfig() + .withConnectionUrl("https://" + host)); + } + + public static CollectionModel createCollection(PineconeControlPlaneClient controlPlaneClient, String collectionName, String indexName, boolean waitUntilReady) throws InterruptedException { + CreateCollectionRequest createCollectionRequest = new CreateCollectionRequest().name(collectionName).source(indexName); + CollectionModel collection = controlPlaneClient.createCollection(createCollectionRequest); + + assertEquals(collection.getStatus(), CollectionModel.StatusEnum.INITIALIZING); + + // Wait until collection is ready + int timeWaited = 0; + CollectionModel.StatusEnum collectionReady = collection.getStatus(); + while (collectionReady != CollectionModel.StatusEnum.READY && timeWaited < 120000) { + System.out.println("Waiting for collection" + collectionName + " to be ready. Waited " + timeWaited + " milliseconds..."); + Thread.sleep(5000); + timeWaited += 5000; + collection = controlPlaneClient.describeCollection(collectionName); + collectionReady = collection.getStatus(); + } + + if (timeWaited > 120000) { + fail("Collection: " + collectionName + " is not ready after 120 seconds"); + } + + return collection; + } + public static IndexModel isIndexReady(String indexName, PineconeControlPlaneClient controlPlaneClient) throws InterruptedException { final IndexModel[] indexModels = new IndexModel[1]; diff --git a/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionErrorTest.java b/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionErrorTest.java new file mode 100644 index 00000000..00ea279f --- /dev/null +++ b/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionErrorTest.java @@ -0,0 +1,143 @@ +package io.pinecone.integration.controlPlane.pod; + +import io.pinecone.PineconeConnection; +import io.pinecone.PineconeControlPlaneClient; +import io.pinecone.exceptions.PineconeException; +import io.pinecone.helpers.RandomStringBuilder; +import io.pinecone.proto.VectorServiceGrpc; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.openapitools.client.model.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import static io.pinecone.helpers.IndexManager.createNewIndexAndConnect; +import static io.pinecone.helpers.IndexManager.createCollection; +import static io.pinecone.helpers.IndexManager.waitUntilIndexIsReady; +import static io.pinecone.helpers.BuildUpsertRequest.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CollectionErrorTest { + private static final String apiKey = System.getenv("PINECONE_API_KEY"); + private static final String environment = System.getenv("PINECONE_ENVIRONMENT"); + private static final String indexName = RandomStringBuilder.build("collection-error-test", 8); + private static final List upsertIds = Arrays.asList("v1", "v2", "v3"); + private static final int dimension = 3; + private static final String collectionName = RandomStringBuilder.build("reusable-coll", 8); + private static PineconeControlPlaneClient controlPlaneClient; + + @BeforeAll + public static void setUpIndexAndCollection() throws InterruptedException { + controlPlaneClient = new PineconeControlPlaneClient(apiKey); + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().pods(1).podType("p1.x1").replicas(1).environment(environment); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + PineconeConnection dataPlaneConnection = createNewIndexAndConnect(controlPlaneClient, indexName, dimension, IndexMetric.COSINE, spec); + VectorServiceGrpc.VectorServiceBlockingStub blockingStub = dataPlaneConnection.getBlockingStub(); + + // Upsert vectors to index and sleep for freshness + blockingStub.upsert(buildRequiredUpsertRequest(upsertIds, "")); + dataPlaneConnection.close(); + + Thread.sleep(3500); + + // Create collection from index + createCollection(controlPlaneClient, collectionName, indexName, true); + } + + @AfterAll + public static void cleanUp() { + controlPlaneClient.deleteIndex(indexName); + controlPlaneClient.deleteCollection(collectionName); + } + + @Test + public void testCreateCollectionFromInvalidIndex() { + try { + CreateCollectionRequest createCollectionRequest = new CreateCollectionRequest().name(RandomStringBuilder.build("coll1", 8)).source("invalid-index"); + controlPlaneClient.createCollection(createCollectionRequest); + } catch (PineconeException exception) { + assertTrue(exception.getMessage().contains("Resource invalid-index not found")); + } + } + @Test + public void testIndexFromNonExistentCollection() { + try { + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().environment(environment).sourceCollection("non-existent-collection"); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + CreateIndexRequest newCreateIndexRequest = new CreateIndexRequest().name(RandomStringBuilder.build("from-nonexistent-coll", 8)).dimension(3).metric(IndexMetric.COSINE).spec(spec); + controlPlaneClient.createIndex(newCreateIndexRequest); + } catch (PineconeException exception) { + assertTrue(exception.getMessage().contains("Resource non-existent-collection not found")); + } + } + + @Test + public void testCreateIndexInMismatchedEnvironment() { + try { + List environments = new LinkedList<>(Arrays.asList( + "eastus-azure", + "eu-west4-gcp", + "northamerica-northeast1-gcp", + "us-central1-gcp", + "us-west4-gcp", + "asia-southeast1-gcp", + "us-east-1-aws", + "asia-northeast1-gcp", + "eu-west1-gcp", + "eu-east1-gcp", + "eu-east4-gcp", + "us-west1-gcp" + )); + CollectionModel collection = controlPlaneClient.describeCollection(collectionName); + environments.remove(collection.getEnvironment()); + String mismatchedEnv = environments.get(new Random().nextInt(environments.size())); + + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().sourceCollection(collection.getName()).environment(mismatchedEnv); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + CreateIndexRequest createIndexRequest = new CreateIndexRequest().name(RandomStringBuilder.build("from-coll-", 8)).dimension(dimension).metric(IndexMetric.COSINE).spec(spec); + controlPlaneClient.createIndex(createIndexRequest); + } catch (PineconeException exception) { + assertTrue(exception.getMessage().contains("Source collection must be in the same environment as the index")); + } + } + + @Test + @Disabled("Bug reported in #global-cps") + public void testCreateIndexWithMismatchedDimension() { + try { + CollectionModel collection = controlPlaneClient.describeCollection(collectionName); + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().sourceCollection(collection.getName()).environment(collection.getEnvironment()); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + CreateIndexRequest createIndexRequest = new CreateIndexRequest().name(RandomStringBuilder.build("from-coll-", 8)).dimension(dimension + 1).metric(IndexMetric.COSINE).spec(spec); + controlPlaneClient.createIndex(createIndexRequest); + } catch (PineconeException exception) { + assertTrue(exception.getMessage().contains("Index and collection must have the same dimension")); + } + } + + @Test + public void testCreateCollectionFromNotReadyIndex() throws InterruptedException { + String notReadyIndexName = RandomStringBuilder.build("from-coll4", 8); + try { + CreateIndexRequestSpecPod specPod = new CreateIndexRequestSpecPod().pods(1).podType("p1.x1").replicas(1).environment(environment); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(specPod); + CreateIndexRequest createIndexRequest = new CreateIndexRequest().name(notReadyIndexName).dimension(dimension).metric(IndexMetric.COSINE).spec(spec); + controlPlaneClient.createIndex(createIndexRequest); + + CreateCollectionRequest createCollectionRequest = new CreateCollectionRequest().name(RandomStringBuilder.build("coll4-", 8)).source(notReadyIndexName); + controlPlaneClient.createCollection(createCollectionRequest); + } catch (PineconeException exception) { + assertTrue(exception.getMessage().contains("Source index is not ready")); + + // Wait for index to initialize and clean up + waitUntilIndexIsReady(controlPlaneClient, notReadyIndexName); + controlPlaneClient.deleteIndex(notReadyIndexName); + } + } +} \ No newline at end of file diff --git a/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionTest.java b/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionTest.java new file mode 100644 index 00000000..fecfe091 --- /dev/null +++ b/src/integration/java/io/pinecone/integration/controlPlane/pod/CollectionTest.java @@ -0,0 +1,180 @@ +package io.pinecone.integration.controlPlane.pod; + +import io.pinecone.*; +import io.pinecone.helpers.RandomStringBuilder; +import io.pinecone.proto.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openapitools.client.model.*; + +import java.util.Arrays; +import java.util.List; + +import static io.pinecone.helpers.IndexManager.createNewIndexAndConnect; +import static io.pinecone.helpers.IndexManager.waitUntilIndexIsReady; +import static io.pinecone.helpers.IndexManager.createCollection; +import static io.pinecone.helpers.BuildUpsertRequest.*; +import static org.junit.jupiter.api.Assertions.*; + +public class CollectionTest { + + private static PineconeControlPlaneClient controlPlaneClient; + private static final String indexName = RandomStringBuilder.build("collection-test", 8); + private static final IndexMetric indexMetric = IndexMetric.COSINE; + private static final List upsertIds = Arrays.asList("v1", "v2", "v3"); + private static final String namespace = RandomStringBuilder.build("ns", 8); + private static final String apiKey = System.getenv("PINECONE_API_KEY"); + private static final String environment = System.getenv("PINECONE_ENVIRONMENT"); + private static final int dimension = 3; + + @BeforeAll + public static void setUpIndex() throws InterruptedException { + controlPlaneClient = new PineconeControlPlaneClient(apiKey); + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().pods(1).podType("p1.x1").replicas(1).environment(environment); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + PineconeConnection dataPlaneConnection = createNewIndexAndConnect(controlPlaneClient, indexName, dimension, indexMetric, spec); + VectorServiceGrpc.VectorServiceBlockingStub blockingStub = dataPlaneConnection.getBlockingStub(); + + // Upsert vectors to index and sleep for freshness + blockingStub.upsert(buildRequiredUpsertRequest(upsertIds, namespace)); + Thread.sleep(3500); + dataPlaneConnection.close(); + } + + @AfterAll + public static void deleteIndex() throws InterruptedException { + Thread.sleep(5000); + controlPlaneClient.deleteIndex(indexName); + } + + @Test + public void testIndexToCollectionHappyPath() throws InterruptedException { + String collectionName = RandomStringBuilder.build("collection-test", 8); + + // Create collection from index + CollectionModel collection = createCollection(controlPlaneClient, collectionName, indexName, true); + + assertEquals(collection.getName(), collectionName); + assertEquals(collection.getEnvironment(), environment); + assertEquals(collection.getStatus(), CollectionModel.StatusEnum.READY); + + // Verify collection is listed + List collections = controlPlaneClient.listCollections().getCollections(); + boolean collectionFound = false; + if (collections != null && !collections.isEmpty()) { + for (CollectionModel col : collections) { + if (col.getName().equals(collectionName)) { + collectionFound = true; + break; + } + } + } + + if (!collectionFound) { + fail("Collection " + collectionName + " was not found when listing collections"); + } + + // Verify collection can be described + collection = controlPlaneClient.describeCollection(collectionName); + + assertEquals(collection.getStatus(), CollectionModel.StatusEnum.READY); + assertEquals(collection.getDimension(), dimension); + assertEquals(collection.getVectorCount(), 3); + assertNotEquals(collection.getVectorCount(), null); + assertTrue(collection.getSize() > 0); + + // Create index from collection + String newIndexName = RandomStringBuilder.build("index-from-col", 5); + System.out.println("Creating index " + newIndexName + " from collection " + collectionName); + + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().environment(environment).sourceCollection(collectionName); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + CreateIndexRequest newCreateIndexRequest = new CreateIndexRequest().name(newIndexName).dimension(dimension).metric(indexMetric).spec(spec); + controlPlaneClient.createIndex(newCreateIndexRequest); + System.out.println("Index " + newIndexName + " created from collection " + collectionName + ". Waiting until index is ready..."); + waitUntilIndexIsReady(controlPlaneClient, newIndexName); + + IndexModel indexDescription = controlPlaneClient.describeIndex(newIndexName); + assertEquals(indexDescription.getName(), newIndexName); + assertEquals(indexDescription.getSpec().getPod().getSourceCollection(), collectionName); + assertEquals(indexDescription.getStatus().getReady(), true); + Thread.sleep(5000); + + // Set up new index data plane connection + PineconeClient newIndexClient = new PineconeClient(new PineconeClientConfig().withApiKey(apiKey).withEnvironment(environment)); + PineconeConnection newIndexDataPlaneClient = newIndexClient.connect(new PineconeConnectionConfig().withConnectionUrl("https://" + indexDescription.getHost())); + VectorServiceGrpc.VectorServiceBlockingStub newIndexBlockingStub = newIndexDataPlaneClient.getBlockingStub(); + DescribeIndexStatsResponse describeResponse = newIndexBlockingStub.describeIndexStats(DescribeIndexStatsRequest.newBuilder().build()); + + // Verify stats reflect the vectors in the collection + assertEquals(describeResponse.getTotalVectorCount(), 3); + + // Verify the vectors from the collection -> new index can be fetched + FetchResponse fetchedVectors = newIndexBlockingStub.fetch(FetchRequest.newBuilder().addAllIds(upsertIds).setNamespace(namespace).build()); + newIndexDataPlaneClient.close(); + + for (String key : upsertIds) { + assert (fetchedVectors.containsVectors(key)); + } + + // Verify we can delete the collection + controlPlaneClient.deleteCollection(collectionName); + Thread.sleep(2500); + collections = controlPlaneClient.listCollections().getCollections(); + + + if (collections != null) { + boolean isCollectionDeleted = true; + for (CollectionModel col : collections) { + if (col.getName().equals(collectionName)) { + isCollectionDeleted = false; + break; + } + } + + if (!isCollectionDeleted) { + fail("Collection " + collectionName + " was not successfully deleted"); + } + } + + // Clean up + controlPlaneClient.deleteIndex(newIndexName); + } + + @Test + public void testIndexFromDifferentMetricCollection() throws InterruptedException { + String collectionName = RandomStringBuilder.build("collection-test", 8); + + // Create collection from index + CollectionModel collection = createCollection(controlPlaneClient, collectionName, indexName, true); + + assertEquals(collection.getName(), collectionName); + assertEquals(collection.getEnvironment(), environment); + assertEquals(collection.getStatus(), CollectionModel.StatusEnum.READY); + + // Use a different metric than the source index + IndexMetric[] metrics = { IndexMetric.COSINE, IndexMetric.EUCLIDEAN, IndexMetric.DOTPRODUCT }; + IndexMetric targetMetric = IndexMetric.COSINE; + for (IndexMetric metric : metrics) { + if (!metric.equals(indexMetric)) { + targetMetric = metric; + } + } + + String newIndexName = RandomStringBuilder.build("from-coll", 8); + CreateIndexRequestSpecPod podSpec = new CreateIndexRequestSpecPod().environment(environment).sourceCollection(collectionName); + CreateIndexRequestSpec spec = new CreateIndexRequestSpec().pod(podSpec); + PineconeConnection dataPlaneConnection = createNewIndexAndConnect(controlPlaneClient, newIndexName, dimension, targetMetric, spec); + + IndexModel newIndex = controlPlaneClient.describeIndex(newIndexName); + assertEquals(newIndex.getName(), newIndexName); + assertEquals(newIndex.getMetric(), targetMetric); + + // Clean up + controlPlaneClient.deleteIndex(newIndexName); + controlPlaneClient.deleteCollection(collectionName); + dataPlaneConnection.close(); + } + +} \ No newline at end of file diff --git a/src/integration/java/io/pinecone/integration/controlPlane/index/pod/ConfigureIndexTest.java b/src/integration/java/io/pinecone/integration/controlPlane/pod/ConfigureIndexTest.java similarity index 99% rename from src/integration/java/io/pinecone/integration/controlPlane/index/pod/ConfigureIndexTest.java rename to src/integration/java/io/pinecone/integration/controlPlane/pod/ConfigureIndexTest.java index 4e49cdc5..9e26938c 100644 --- a/src/integration/java/io/pinecone/integration/controlPlane/index/pod/ConfigureIndexTest.java +++ b/src/integration/java/io/pinecone/integration/controlPlane/pod/ConfigureIndexTest.java @@ -1,4 +1,4 @@ -package io.pinecone.integration.controlPlane.index.pod; +package io.pinecone.integration.controlPlane.pod; import io.pinecone.PineconeControlPlaneClient; import io.pinecone.exceptions.PineconeException; diff --git a/src/integration/java/io/pinecone/integration/controlPlane/index/pod/CreateDescribeListAndDeleteIndexTest.java b/src/integration/java/io/pinecone/integration/controlPlane/pod/CreateDescribeListAndDeleteIndexTest.java similarity index 97% rename from src/integration/java/io/pinecone/integration/controlPlane/index/pod/CreateDescribeListAndDeleteIndexTest.java rename to src/integration/java/io/pinecone/integration/controlPlane/pod/CreateDescribeListAndDeleteIndexTest.java index b031e7a6..18333ee6 100644 --- a/src/integration/java/io/pinecone/integration/controlPlane/index/pod/CreateDescribeListAndDeleteIndexTest.java +++ b/src/integration/java/io/pinecone/integration/controlPlane/pod/CreateDescribeListAndDeleteIndexTest.java @@ -1,4 +1,4 @@ -package io.pinecone.integration.controlPlane.index.pod; +package io.pinecone.integration.controlPlane.pod; import io.pinecone.PineconeControlPlaneClient; import io.pinecone.helpers.RandomStringBuilder; diff --git a/src/integration/java/io/pinecone/integration/controlPlane/index/serverless/CreateDescribeListAndDeleteIndexTest.java b/src/integration/java/io/pinecone/integration/controlPlane/serverless/CreateDescribeListAndDeleteIndexTest.java similarity index 96% rename from src/integration/java/io/pinecone/integration/controlPlane/index/serverless/CreateDescribeListAndDeleteIndexTest.java rename to src/integration/java/io/pinecone/integration/controlPlane/serverless/CreateDescribeListAndDeleteIndexTest.java index df9a4dba..d39683cf 100644 --- a/src/integration/java/io/pinecone/integration/controlPlane/index/serverless/CreateDescribeListAndDeleteIndexTest.java +++ b/src/integration/java/io/pinecone/integration/controlPlane/serverless/CreateDescribeListAndDeleteIndexTest.java @@ -1,4 +1,4 @@ -package io.pinecone.integration.controlPlane.index.serverless; +package io.pinecone.integration.controlPlane.serverless; import io.pinecone.PineconeControlPlaneClient; import io.pinecone.helpers.RandomStringBuilder; diff --git a/src/main/java/io/pinecone/PineconeControlPlaneClient.java b/src/main/java/io/pinecone/PineconeControlPlaneClient.java index f8c3e09d..1b1b5a51 100644 --- a/src/main/java/io/pinecone/PineconeControlPlaneClient.java +++ b/src/main/java/io/pinecone/PineconeControlPlaneClient.java @@ -2,15 +2,13 @@ import io.pinecone.exceptions.FailedRequestInfo; import io.pinecone.exceptions.HttpErrorMapper; +import io.pinecone.exceptions.PineconeException; import io.pinecone.exceptions.PineconeValidationException; import okhttp3.*; import org.openapitools.client.ApiClient; import org.openapitools.client.ApiException; import org.openapitools.client.api.ManageIndexesApi; -import org.openapitools.client.model.ConfigureIndexRequest; -import org.openapitools.client.model.CreateIndexRequest; -import org.openapitools.client.model.IndexList; -import org.openapitools.client.model.IndexModel; +import org.openapitools.client.model.*; public class PineconeControlPlaneClient { private ManageIndexesApi manageIndexesApi; @@ -29,17 +27,17 @@ public PineconeControlPlaneClient(String apiKey, OkHttpClient okHttpClient) { manageIndexesApi.setApiClient(apiClient); } - public IndexModel createIndex(CreateIndexRequest createIndexRequest) { + public IndexModel createIndex(CreateIndexRequest createIndexRequest) throws PineconeException { IndexModel indexModel = new IndexModel(); try { - manageIndexesApi.createIndex(createIndexRequest); + indexModel = manageIndexesApi.createIndex(createIndexRequest); } catch (ApiException apiException) { handleApiException(apiException); } return indexModel; } - public IndexModel describeIndex(String indexName) { + public IndexModel describeIndex(String indexName) throws PineconeException { IndexModel indexModel = new IndexModel(); try { indexModel = manageIndexesApi.describeIndex(indexName); @@ -49,7 +47,7 @@ public IndexModel describeIndex(String indexName) { return indexModel; } - public void configureIndex(String indexName, ConfigureIndexRequest configureIndexRequest) { + public void configureIndex(String indexName, ConfigureIndexRequest configureIndexRequest) throws PineconeException { try { manageIndexesApi.configureIndex(indexName, configureIndexRequest); } catch (ApiException apiException) { @@ -57,7 +55,7 @@ public void configureIndex(String indexName, ConfigureIndexRequest configureInde } } - public IndexList listIndexes() { + public IndexList listIndexes() throws PineconeException { IndexList indexList = new IndexList(); try { indexList = manageIndexesApi.listIndexes(); @@ -67,7 +65,7 @@ public IndexList listIndexes() { return indexList; } - public void deleteIndex(String indexName) { + public void deleteIndex(String indexName) throws PineconeException { try { manageIndexesApi.deleteIndex(indexName); } catch (ApiException apiException) { @@ -75,6 +73,44 @@ public void deleteIndex(String indexName) { } } + public CollectionModel createCollection(CreateCollectionRequest createCollectionRequest) throws PineconeException { + CollectionModel collection = null; + try { + collection = manageIndexesApi.createCollection(createCollectionRequest); + } catch (ApiException apiException) { + handleApiException(apiException); + } + return collection; + } + + public CollectionModel describeCollection(String collectionName) throws PineconeException { + CollectionModel collection = null; + try { + collection = manageIndexesApi.describeCollection(collectionName); + } catch (ApiException apiException) { + handleApiException(apiException); + } + return collection; + } + + public CollectionList listCollections() throws PineconeException { + CollectionList collections = null; + try { + collections = manageIndexesApi.listCollections(); + } catch (ApiException apiException) { + handleApiException(apiException); + } + return collections; + } + + public void deleteCollection(String collectionName) throws PineconeException { + try { + manageIndexesApi.deleteCollection(collectionName); + } catch (ApiException apiException) { + handleApiException(apiException); + } + } + private void handleApiException(ApiException apiException) { int statusCode = apiException.getCode(); String responseBody = apiException.getResponseBody(); diff --git a/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java b/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java index 4a4ebb73..58c7e2c8 100644 --- a/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java +++ b/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java @@ -2,7 +2,7 @@ public class HttpErrorMapper { - public static void mapHttpStatusError(FailedRequestInfo failedRequestInfo) { + public static void mapHttpStatusError(FailedRequestInfo failedRequestInfo) throws PineconeException { int statusCode = failedRequestInfo.getStatus(); switch (statusCode) { case 400: