diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cdcf934fc..50fe6d1b1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -78,7 +78,8 @@ jobs: strategy: fail-fast: false matrix: - WEAVIATE_VERSION: ["1.32.24", "1.33.11", "1.34.7", "1.35.2"] + WEAVIATE_VERSION: + ["1.32.24", "1.33.11", "1.34.7", "1.35.2", "1.36.0-rc.0"] steps: - uses: actions/checkout@v4 diff --git a/src/it/java/io/weaviate/ConcurrentTest.java b/src/it/java/io/weaviate/ConcurrentTest.java index 6e783ff06..d519d0c7e 100644 --- a/src/it/java/io/weaviate/ConcurrentTest.java +++ b/src/it/java/io/weaviate/ConcurrentTest.java @@ -126,10 +126,37 @@ public static void requireAtLeast(Weaviate.Version required) { .isGreaterThanOrEqualTo(required.semver); } + @FunctionalInterface + public interface ThrowingRunnable { + void run() throws Exception; + } + + /** + * Run a block of code only if the server version is recent enough. + * + * @param required Minimal required version. + * @param r Runnable. + */ public static void requireAtLeast(Weaviate.Version required, Runnable r) { var actual = SemanticVersion.of(Weaviate.VERSION); if (actual.compareTo(required.semver) >= 0) { r.run(); } } + + /** + * Wraps a {@link ThrowingRunnable} as {@link Runnable} that + * re-throws all exceptions as {@link RuntimeException}. + * + * @param tr Runnable which may throw a checked exception. + */ + public static Runnable throwing(ThrowingRunnable tr) { + return () -> { + try { + tr.run(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } } diff --git a/src/it/java/io/weaviate/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index 0f463e96f..c12bf9f6e 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -26,7 +26,7 @@ public class Weaviate extends WeaviateContainer { public static final String DOCKER_IMAGE = "semitechnologies/weaviate"; - public static final String LATEST_VERSION = Version.V135.semver.toString(); + public static final String LATEST_VERSION = Version.latest().semver.toString(); public static final String VERSION; static { @@ -41,7 +41,8 @@ public enum Version { V132(1, 32, 24), V133(1, 33, 11), V134(1, 34, 7), - V135(1, 35, 2); + V135(1, 35, 2), + V136(1, 36, "0-rc.0"); public final SemanticVersion semver; @@ -49,9 +50,21 @@ private Version(int major, int minor, int patch) { this.semver = new SemanticVersion(major, minor, patch); } + private Version(int major, int minor, String patch) { + this.semver = new SemanticVersion(major, minor, patch); + } + public void orSkip() { ConcurrentTest.requireAtLeast(this); } + + public static Version latest() { + Version[] versions = Version.class.getEnumConstants(); + if (versions == null) { + throw new IllegalStateException("No versions are defined"); + } + return versions[versions.length - 1]; + } } /** diff --git a/src/it/java/io/weaviate/integration/BackupITest.java b/src/it/java/io/weaviate/integration/BackupITest.java index c540afaae..0f51ca724 100644 --- a/src/it/java/io/weaviate/integration/BackupITest.java +++ b/src/it/java/io/weaviate/integration/BackupITest.java @@ -14,6 +14,8 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.Test; +import com.sun.nio.sctp.IllegalUnbindException; + import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.backup.Backup; @@ -112,14 +114,22 @@ public void test_lifecycle() throws IOException, TimeoutException { // Act: delete data and restore backup #1 client.collections.delete(nsA); - client.backup.restore(backup_1, backend, restore -> restore.includeCollections(nsA)); + var restoreMe = client.backup.restore(backup_1, backend, restore -> restore.includeCollections(nsA)); // Assert: object inserted in the beginning of the test is present - var restore_1 = client.backup.getRestoreStatus(backup_1, backend) - .orElseThrow().waitForCompletion(client); - Assertions.assertThat(restore_1).as("restore backup #1") + restoreMe = restoreMe.waitForCompletion(client); + Assertions.assertThat(restoreMe).as("restore backup #1") .returns(BackupStatus.SUCCESS, Backup::status); Assertions.assertThat(collectionA.size()).as("after restore backup #1").isEqualTo(1); + + // Act: restore and cancel + requireAtLeast(Weaviate.Version.V136, throwing(() -> { + var restore_2 = client.backup.restore(backup_2, backend); + client.backup.cancelRestore(backup_2, backend); + var canceledRestore = restore_2.waitForStatus(client, BackupStatus.CANCELED); + Assertions.assertThat(canceledRestore).as("cancel backup restore #2") + .returns(BackupStatus.CANCELED, Backup::status); + })); } @Test @@ -215,13 +225,11 @@ public void test_lifecycle_async() throws ExecutionException, InterruptedExcepti // Act: delete data and restore backup #1 async.collections.delete(nsA).join(); - async.backup.restore(backup_1, backend, restore -> restore.includeCollections(nsA)).join(); + var restoreMe = async.backup.restore(backup_1, backend, restore -> restore.includeCollections(nsA)).join(); // Assert: object inserted in the beginning of the test is present - var restore_1 = async.backup.getRestoreStatus(backup_1, backend) - .thenCompose(bak -> bak.orElseThrow().waitForCompletion(async)) - .join(); - Assertions.assertThat(restore_1).as("restore backup #1") + restoreMe = restoreMe.waitForCompletion(async).join(); + Assertions.assertThat(restoreMe).as("restore backup #1") .returns(BackupStatus.SUCCESS, Backup::status); Assertions.assertThat(collectionA.size().join()).as("after restore backup #1").isEqualTo(1); } diff --git a/src/main/java/io/weaviate/client6/v1/api/backup/BackupStatus.java b/src/main/java/io/weaviate/client6/v1/api/backup/BackupStatus.java index 7247fade5..5e598d1f4 100644 --- a/src/main/java/io/weaviate/client6/v1/api/backup/BackupStatus.java +++ b/src/main/java/io/weaviate/client6/v1/api/backup/BackupStatus.java @@ -11,8 +11,8 @@ public enum BackupStatus { TRANSFERRING, /** * Cancellation has been claimed by a coordinator. - * Used as a distributed lock to prevent race conditions when multiple coordinators - * attempt to cancel the same restore. + * Used as a distributed lock to prevent race conditions when multiple + * coordinators attempt to cancel the same restore. */ @SerializedName("CANCELLING") CANCELLING, @@ -28,10 +28,7 @@ public enum BackupStatus { /** Backup creation / restoration failed. */ @SerializedName("FAILED") FAILED, - /** - * Backup creation canceled. - * This status is never returned for backup restorations. - */ + /** Backup creation canceled. */ @SerializedName("CANCELED") CANCELED; } diff --git a/src/main/java/io/weaviate/client6/v1/api/backup/CancelBackupRestoreRequest.java b/src/main/java/io/weaviate/client6/v1/api/backup/CancelBackupRestoreRequest.java new file mode 100644 index 000000000..98bb3234f --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/backup/CancelBackupRestoreRequest.java @@ -0,0 +1,14 @@ +package io.weaviate.client6.v1.api.backup; + +import java.util.Collections; + +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.SimpleEndpoint; + +public record CancelBackupRestoreRequest(String backupId, String backend) { + + public static Endpoint _ENDPOINT = SimpleEndpoint.sideEffect( + request -> "DELETE", + request -> "/backups/" + request.backend + "/" + request.backupId + "/restore", + request -> Collections.emptyMap()); +} diff --git a/src/main/java/io/weaviate/client6/v1/api/backup/WeaviateBackupClient.java b/src/main/java/io/weaviate/client6/v1/api/backup/WeaviateBackupClient.java index 90bf70b8a..ac33c3e21 100644 --- a/src/main/java/io/weaviate/client6/v1/api/backup/WeaviateBackupClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/backup/WeaviateBackupClient.java @@ -179,9 +179,6 @@ public List list(String backend, Function - * This method cannot be called cancel backup restore. - * * @param backupId Backup ID. * @param backend Backup storage backend. * @throws WeaviateApiException in case the server returned with an @@ -189,8 +186,42 @@ public List list(String backend, Function> list(String backend, } /** - * Cancel in-progress backup. - * - *

- * This method cannot be called cancel backup restore. + * Cancel in-progress backup creation. * * @param backupId Backup ID. * @param backend Backup storage backend. + * @deprecated This method forwards to {@link #cancelCreate}. Prefer using the + * latter, as it is less ambiguous. */ + @Deprecated public CompletableFuture cancel(String backupId, String backend) { + return cancelCreate(backupId, backend); + } + + /** + * Cancel in-progress backup creation. + * + * @param backupId Backup ID. + * @param backend Backup storage backend. + */ + public CompletableFuture cancelCreate(String backupId, String backend) { return this.restTransport.performRequestAsync(new CancelBackupRequest(backupId, backend), CancelBackupRequest._ENDPOINT); } + + /** + * Cancel in-progress backup restore. + * + * @param backupId Backup ID. + * @param backend Backup storage backend. + */ + public CompletableFuture cancelRestore(String backupId, String backend) { + return this.restTransport.performRequestAsync(new CancelBackupRestoreRequest(backupId, backend), + CancelBackupRestoreRequest._ENDPOINT); + } }