diff --git a/api/src/main/java/io/strimzi/api/kafka/model/kafka/PersistentClaimStorage.java b/api/src/main/java/io/strimzi/api/kafka/model/kafka/PersistentClaimStorage.java index b0d1c7d0269..7a1a102c449 100644 --- a/api/src/main/java/io/strimzi/api/kafka/model/kafka/PersistentClaimStorage.java +++ b/api/src/main/java/io/strimzi/api/kafka/model/kafka/PersistentClaimStorage.java @@ -26,7 +26,7 @@ editableEnabled = false, builderPackage = Constants.FABRIC8_KUBERNETES_API ) -@JsonPropertyOrder({"id", "type", "size", "kraftMetadata", "class", "selector", "deleteClaim", "overrides"}) +@JsonPropertyOrder({"id", "type", "size", "kraftMetadata", "class", "volumeAttributesClass", "selector", "deleteClaim", "overrides"}) @JsonInclude(JsonInclude.Include.NON_DEFAULT) @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @@ -36,6 +36,7 @@ public class PersistentClaimStorage extends SingleVolumeStorage { private Map selector; private boolean deleteClaim; private List overrides; + private String volumeAttributesClass; @Description("Must be `" + TYPE_PERSISTENT_CLAIM + "`") @Override @@ -125,4 +126,13 @@ public List getOverrides() { public void setOverrides(List overrides) { this.overrides = overrides; } + + @Description("Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes.") + public String getVolumeAttributesClass() { + return this.volumeAttributesClass; + } + + public void setVolumeAttributesClass(String volumeAttributesClass) { + this.volumeAttributesClass = volumeAttributesClass; + } } diff --git a/api/src/test/resources/crds/v1/045-Crd-kafkanodepool.yaml b/api/src/test/resources/crds/v1/045-Crd-kafkanodepool.yaml index 1d479a6fb64..9d1b80afd6c 100644 --- a/api/src/test/resources/crds/v1/045-Crd-kafkanodepool.yaml +++ b/api/src/test/resources/crds/v1/045-Crd-kafkanodepool.yaml @@ -98,6 +98,9 @@ spec: - persistent-claim - jbod description: "Storage type, must be either 'ephemeral', 'persistent-claim', or 'jbod'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. volumes: type: array items: @@ -136,6 +139,9 @@ spec: - ephemeral - persistent-claim description: "Storage type, must be either 'ephemeral' or 'persistent-claim'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. required: - type description: List of volumes as Storage objects representing the JBOD disks array. diff --git a/api/src/test/resources/crds/v1beta2/040-Crd-kafka.yaml b/api/src/test/resources/crds/v1beta2/040-Crd-kafka.yaml index 5769639f057..4b03b1d12ad 100644 --- a/api/src/test/resources/crds/v1beta2/040-Crd-kafka.yaml +++ b/api/src/test/resources/crds/v1beta2/040-Crd-kafka.yaml @@ -5396,6 +5396,8 @@ spec: - ephemeral - persistent-claim - jbod + volumeAttributesClass: + type: string volumes: type: array items: @@ -5435,6 +5437,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type required: @@ -6944,6 +6948,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type config: diff --git a/api/src/test/resources/crds/v1beta2/045-Crd-kafkanodepool.yaml b/api/src/test/resources/crds/v1beta2/045-Crd-kafkanodepool.yaml index b75d8165f58..60e62bf6dbf 100644 --- a/api/src/test/resources/crds/v1beta2/045-Crd-kafkanodepool.yaml +++ b/api/src/test/resources/crds/v1beta2/045-Crd-kafkanodepool.yaml @@ -89,6 +89,8 @@ spec: - ephemeral - persistent-claim - jbod + volumeAttributesClass: + type: string volumes: type: array items: @@ -119,6 +121,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type required: @@ -1266,6 +1270,8 @@ spec: - ephemeral - persistent-claim - jbod + volumeAttributesClass: + type: string volumes: type: array items: @@ -1305,6 +1311,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type required: diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtils.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtils.java index 445e9f0e6e5..b518efcb307 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtils.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtils.java @@ -114,6 +114,7 @@ private static PersistentVolumeClaim createPersistentVolumeClaim( .withRequests(requests) .endResources() .withStorageClassName(storage.getStorageClass()) + .withVolumeAttributesClassName(storage.getVolumeAttributesClass()) .withSelector(storageSelector) .withVolumeMode("Filesystem") .endSpec() diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/StorageDiff.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/StorageDiff.java index 1f659839afb..5dba608b746 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/StorageDiff.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/StorageDiff.java @@ -28,7 +28,7 @@ public class StorageDiff extends AbstractJsonDiff { private static final ReconciliationLogger LOGGER = ReconciliationLogger.create(StorageDiff.class.getName()); private static final Pattern IGNORABLE_PATHS = Pattern.compile( - "^(/deleteClaim|/kraftMetadata|/overrides.*|/)$"); + "^(/deleteClaim|/kraftMetadata|/overrides.*|/volumeAttributesClass|/)$"); private final boolean isEmpty; private final boolean changesType; diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtilsTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtilsTest.java index 61314ef0d46..f74f9eac95e 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtilsTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/PersistentVolumeClaimUtilsTest.java @@ -45,6 +45,7 @@ public class PersistentVolumeClaimUtilsTest { .build(); private final static PersistentClaimStorage PERSISTENT_CLAIM_STORAGE = new PersistentClaimStorageBuilder() .withStorageClass("my-storage-class") + .withVolumeAttributesClass("my-volume-attributes-class") .withSize("100Gi") .build(); private final static Set SINGLE_NODE = Set.of(new NodeRef(NAME + "-" + 0, 0, null, false, true)); @@ -95,6 +96,7 @@ public void testPersistentClaimStorage() { assertThat(pvcs.get(0).getSpec().getSelector(), is(nullValue())); assertThat(pvcs.get(0).getSpec().getResources().getRequests(), is(Map.of("storage", new Quantity("100Gi", null)))); assertThat(pvcs.get(0).getSpec().getStorageClassName(), is("my-storage-class")); + assertThat(pvcs.get(0).getSpec().getVolumeAttributesClassName(), is("my-volume-attributes-class")); } @Test diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/StorageDiffTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/StorageDiffTest.java index 87ce611af93..6e42e4e8e80 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/StorageDiffTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/StorageDiffTest.java @@ -361,4 +361,42 @@ public void testDuplicateVolumeIds() { assertThat(diff.isDuplicateVolumeIds(), is(true)); assertThat(diff.issuesDetected(), is(true)); } + + @Test + public void testVolumeAttributesChanges() { + Storage persistent = new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withDeleteClaim(false).withId(0).withSize("100Gi").build(); + Storage persistent2 = new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withDeleteClaim(false).withId(0).withSize("100Gi").withVolumeAttributesClass("vac-example").build(); + Storage persistent3 = new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withDeleteClaim(false).withId(0).withSize("100Gi").withVolumeAttributesClass("vac-example-2").build(); + + StorageDiff diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, persistent, persistent, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + + diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, persistent, persistent2, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + + diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, persistent2, persistent3, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + + Storage jbod = new JbodStorageBuilder().withVolumes( + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(0).withVolumeAttributesClass("vac-example-1").build(), + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(1).withVolumeAttributesClass("vac-example-2").build() + ).build(); + Storage jbod2 = new JbodStorageBuilder().withVolumes( + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(0).build(), + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(1).withVolumeAttributesClass("vac-example-2").build() + ).build(); + Storage jbod3 = new JbodStorageBuilder().withVolumes( + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(0).withVolumeAttributesClass("vac-example-2").build(), + new PersistentClaimStorageBuilder().withStorageClass("gp2-ssd").withId(1).withVolumeAttributesClass("vac-example-2").build() + ).build(); + + diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, jbod, jbod, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + + diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, jbod, jbod2, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + + diff = new StorageDiff(Reconciliation.DUMMY_RECONCILIATION, jbod, jbod3, Set.of(0, 1, 2), Set.of(0, 1, 2)); + assertThat(diff.issuesDetected(), is(false)); + } } diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/JbodStorageMockTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/JbodStorageMockTest.java index f7e217c93db..41a1db70abe 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/JbodStorageMockTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/JbodStorageMockTest.java @@ -39,7 +39,6 @@ import io.vertx.junit5.Checkpoint; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; -import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -54,10 +53,14 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; @ExtendWith(VertxExtension.class) public class JbodStorageMockTest { @@ -90,6 +93,7 @@ public static void beforeAll() { .withPodController() .withServiceController() .withDeletionController() + .withPvcController() .build(); mockKube.start(); client = mockKube.client(); @@ -190,7 +194,7 @@ public void testJbodStorageCreatesPVCsMatchingKafkaVolumes(VertxTestContext cont .filter(pvc -> pvc.getMetadata().getName().equals(expectedPvcName)) .collect(Collectors.toList()); assertThat("Exactly one pvc should have the name " + expectedPvcName + " in :\n" + pvcs, - matchingPvcs, Matchers.hasSize(1)); + matchingPvcs, hasSize(1)); PersistentVolumeClaim pvc = matchingPvcs.get(0); boolean isDeleteClaim = ((PersistentClaimStorage) volume).isDeleteClaim(); @@ -320,6 +324,78 @@ public void testReconcileWithUpdateVolumeIdJbod(VertxTestContext context) { }))); } + @Test + public void testReconcileWithUpdateVolumeAttributeClass(VertxTestContext context) { + assertThat(volumes.get(0), is(instanceOf(PersistentClaimStorage.class))); + PersistentClaimStorage storage = (PersistentClaimStorage) volumes.get(0); + storage.setVolumeAttributesClass("vac-example"); + volumes.set(0, storage); + + KafkaNodePool pool = new KafkaNodePoolBuilder(kafkaNodePool) + .editSpec() + .withNewJbodStorage() + .withVolumes(volumes) + .endJbodStorage() + .endSpec() + .build(); + + Set expectedPvcs = expectedPvcs(kafkaNodePool); + + operator.reconcile(new Reconciliation("test-trigger", Kafka.RESOURCE_KIND, namespace, NAME)) + .onComplete(context.succeeding(v -> context.verify(() -> { + List pvcs = getPvcs(); + Set pvcsNames = pvcs.stream().map(pvc -> pvc.getMetadata().getName()) + .collect(Collectors.toSet()); + assertThat(pvcsNames, is(expectedPvcs)); + }))) + .compose(v -> { + client.persistentVolumeClaims() + .waitUntilCondition(pvc -> "Bound".equals(pvc.getStatus().getPhase()), + 5, TimeUnit.SECONDS); + Crds.kafkaNodePoolOperation(client).inNamespace(namespace).withName(NODE_POOL_NAME).patch(pool); + return operator.reconcile(new Reconciliation("vac-added-trigger", Kafka.RESOURCE_KIND, namespace, NAME)); + }) + .onComplete(context.succeeding(v -> context.verify(() -> { + List pvcs = getPvcs(); + Set vacPvc = pvcs.stream() + .filter(pvc -> "vac-example".equals(pvc.getSpec().getVolumeAttributesClassName())) + .map(pvc -> pvc.getMetadata().getName()) + .collect(Collectors.toSet()); + assertThat(vacPvc, hasSize(3)); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-0")); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-1")); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-2")); + }))) + .compose(v -> { + storage.setVolumeAttributesClass("vac-new"); + volumes.set(0, storage); + KafkaNodePool pool1 = new KafkaNodePoolBuilder(pool) + .editSpec() + .withNewJbodStorage() + .withVolumes(volumes) + .endJbodStorage() + .endSpec() + .build(); + client.persistentVolumeClaims() + .waitUntilCondition(pvc -> "Bound".equals(pvc.getStatus().getPhase()), + 5, TimeUnit.SECONDS); + Crds.kafkaNodePoolOperation(client).inNamespace(namespace).withName(NODE_POOL_NAME).patch(pool1); + return operator.reconcile(new Reconciliation("vac-updated-trigger", Kafka.RESOURCE_KIND, namespace, NAME)); + }) + .onComplete(context.succeeding(v -> context.verify(() -> { + List pvcs = getPvcs(); + Set vacPvc = pvcs.stream() + .filter(pvc -> "vac-new".equals(pvc.getSpec().getVolumeAttributesClassName())) + .map(pvc -> pvc.getMetadata().getName()) + .collect(Collectors.toSet()); + assertThat(vacPvc, hasSize(3)); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-0")); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-1")); + assertThat(vacPvc, hasItem("data-0-my-cluster-mixed-2")); + context.completeNow(); + }))); + } + private Set expectedPvcs(KafkaNodePool nodePool) { Set expectedPvcs = new HashSet<>(); for (int i = 0; i < nodePool.getSpec().getReplicas(); i++) { diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java index 1a41c64c6ed..47ebe3fbb42 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java @@ -245,7 +245,13 @@ public static Iterable data() { .withSize("123") .withStorageClass("foo") .withDeleteClaim(true) - .build() + .build(), + new PersistentClaimStorageBuilder() + .withSize("123") + .withStorageClass("foo") + .withVolumeAttributesClass("fooVac") + .withDeleteClaim(true) + .build(), }; List> configs = asList( diff --git a/documentation/modules/appendix_crds.adoc b/documentation/modules/appendix_crds.adoc index e56643c9917..1dbc0e263ce 100644 --- a/documentation/modules/appendix_crds.adoc +++ b/documentation/modules/appendix_crds.adoc @@ -681,6 +681,9 @@ It must have the value `persistent-claim` for the type `PersistentClaimStorage`. |class |string |The storage class to use for dynamic volume allocation. +|volumeAttributesClass +|string +|Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. |selector |map |Specifies a specific persistent volume to use. It contains key:value pairs representing labels for selecting such a volume. diff --git a/mockkube/src/main/java/io/strimzi/test/mockkube3/MockKube3.java b/mockkube/src/main/java/io/strimzi/test/mockkube3/MockKube3.java index 79b43f46942..3fc5fd12851 100644 --- a/mockkube/src/main/java/io/strimzi/test/mockkube3/MockKube3.java +++ b/mockkube/src/main/java/io/strimzi/test/mockkube3/MockKube3.java @@ -24,6 +24,7 @@ import io.strimzi.test.mockkube3.controllers.MockDeletionController; import io.strimzi.test.mockkube3.controllers.MockDeploymentController; import io.strimzi.test.mockkube3.controllers.MockPodController; +import io.strimzi.test.mockkube3.controllers.MockPvcController; import io.strimzi.test.mockkube3.controllers.MockServiceController; import org.testcontainers.utility.DockerImageName; @@ -359,6 +360,16 @@ public MockKube3Builder withNamespaces(String... namespaces) { return this; } + /** + * Registers PVC Controller to manage binding the PVC. + * + * @return MockKube builder instance + */ + public MockKube3Builder withPvcController() { + mock.registerController(new MockPvcController()); + return this; + } + /** * Builds an instance of MockKube based on the builder configuration * diff --git a/mockkube/src/main/java/io/strimzi/test/mockkube3/controllers/MockPvcController.java b/mockkube/src/main/java/io/strimzi/test/mockkube3/controllers/MockPvcController.java new file mode 100644 index 00000000000..ac72dc11464 --- /dev/null +++ b/mockkube/src/main/java/io/strimzi/test/mockkube3/controllers/MockPvcController.java @@ -0,0 +1,69 @@ +/* + * Copyright Strimzi authors. + * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html). + */ +package io.strimzi.test.mockkube3.controllers; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; +import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder; +import io.fabric8.kubernetes.api.model.PersistentVolumeClaimStatusBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The {@link MockPvcController} partially emulates a CSI driver. When new PVC is created + * or modified, it updates its phase to Bound. It doesn't create or bind any {@code PersistentVolumes}. + */ +public class MockPvcController extends AbstractMockController { + private static final Logger LOGGER = LogManager.getLogger(MockPvcController.class); + + private Watch watch; + + @Override + @SuppressFBWarnings({"SIC_INNER_SHOULD_BE_STATIC_ANON"}) // Just a test util, no need to complicate the code bay factoring the anonymous watcher class out + public void start(KubernetesClient client) { + watch = client.persistentVolumeClaims().watch(new Watcher<>() { + @Override + public void eventReceived(Action action, PersistentVolumeClaim pvc) { + try { + switch (action) { + case ADDED: + case MODIFIED: + client.persistentVolumeClaims().resource(new PersistentVolumeClaimBuilder(pvc) + .withStatus(new PersistentVolumeClaimStatusBuilder() + .withPhase("Bound") + .build()) + .build()) + .updateStatus(); + break; + default: + } + } catch (KubernetesClientException e) { + if (e.getCode() == 409) { + LOGGER.info("PVC {} in namespace {} changed while trying to update status", pvc.getMetadata().getName(), pvc.getMetadata().getNamespace()); + } else if (e.getCode() == 404) { + LOGGER.info("PVC {} in namespace {} does not exist anymore", pvc.getMetadata().getName(), pvc.getMetadata().getNamespace()); + } else { + LOGGER.error("Failed to update status of PVC {} in namespace {}", pvc.getMetadata().getName(), pvc.getMetadata().getNamespace(), e); + } + } + } + + @Override + public void onClose(WatcherException e) { + LOGGER.error("Mock PVC controller watch closed", e); + } + }); + } + + @Override + public void stop() { + watch.close(); + } +} diff --git a/mockkube/src/test/java/io/strimzi/test/mockkube3/MockKube3ControllersMockTest.java b/mockkube/src/test/java/io/strimzi/test/mockkube3/MockKube3ControllersMockTest.java index 85878f7c773..f868b046574 100644 --- a/mockkube/src/test/java/io/strimzi/test/mockkube3/MockKube3ControllersMockTest.java +++ b/mockkube/src/test/java/io/strimzi/test/mockkube3/MockKube3ControllersMockTest.java @@ -54,6 +54,7 @@ public static void beforeAll() { .withPodController() .withServiceController() .withDeletionController() + .withPvcController() .build(); mockKube.start(); client = mockKube.client(); @@ -212,4 +213,36 @@ public void testPVCDeletion() { client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).waitUntilCondition(Objects::isNull, 10_000, TimeUnit.MILLISECONDS); assertThat(client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).get(), CoreMatchers.is(nullValue())); } + + @Test + public void testPvcController() { + String pvcName = "my-pvc"; + + PersistentVolumeClaim pvc = new PersistentVolumeClaimBuilder() + .withNewMetadata() + .withNamespace(namespace) + .withName(pvcName) + .endMetadata() + .withNewSpec() + .withNewResources() + .withRequests(singletonMap("storage", new Quantity("100Gi"))) + .endResources() + .withAccessModes("ReadWriteOnce") + .endSpec() + .build(); + + client.persistentVolumeClaims().inNamespace(namespace).resource(pvc).create(); + + client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName) + .waitUntilCondition(cPvc -> "Bound".equals(cPvc.getStatus().getPhase()), 5, TimeUnit.SECONDS); + + PersistentVolumeClaim createdPvc = client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).get(); + assertThat(createdPvc, is(notNullValue())); + assertThat(createdPvc.getStatus(), is(notNullValue())); + assertThat(createdPvc.getStatus().getPhase(), is("Bound")); + + client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).withPropagationPolicy(DeletionPropagation.BACKGROUND).delete(); + client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).waitUntilCondition(Objects::isNull, 10_000, TimeUnit.MILLISECONDS); + assertThat(client.persistentVolumeClaims().inNamespace(namespace).withName(pvcName).get(), CoreMatchers.is(nullValue())); + } } diff --git a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml index 5dfc2280c29..d35134fc3c0 100644 --- a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml +++ b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml @@ -5397,6 +5397,8 @@ spec: - ephemeral - persistent-claim - jbod + volumeAttributesClass: + type: string volumes: type: array items: @@ -5436,6 +5438,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type required: @@ -6945,6 +6949,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type config: diff --git a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/045-Crd-kafkanodepool.yaml b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/045-Crd-kafkanodepool.yaml index 8a1d957441b..e47e0dc7a55 100644 --- a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/045-Crd-kafkanodepool.yaml +++ b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/045-Crd-kafkanodepool.yaml @@ -99,6 +99,9 @@ spec: - persistent-claim - jbod description: "Storage type, must be either 'ephemeral', 'persistent-claim', or 'jbod'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. volumes: type: array items: @@ -137,6 +140,9 @@ spec: - ephemeral - persistent-claim description: "Storage type, must be either 'ephemeral' or 'persistent-claim'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. required: - type description: List of volumes as Storage objects representing the JBOD disks array. @@ -1386,6 +1392,9 @@ spec: - persistent-claim - jbod description: "Storage type, must be either 'ephemeral', 'persistent-claim', or 'jbod'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. volumes: type: array items: @@ -1436,6 +1445,9 @@ spec: - ephemeral - persistent-claim description: "Storage type, must be either 'ephemeral' or 'persistent-claim'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. required: - type description: List of volumes as Storage objects representing the JBOD disks array. diff --git a/packaging/install/cluster-operator/040-Crd-kafka.yaml b/packaging/install/cluster-operator/040-Crd-kafka.yaml index 5769639f057..4b03b1d12ad 100644 --- a/packaging/install/cluster-operator/040-Crd-kafka.yaml +++ b/packaging/install/cluster-operator/040-Crd-kafka.yaml @@ -5396,6 +5396,8 @@ spec: - ephemeral - persistent-claim - jbod + volumeAttributesClass: + type: string volumes: type: array items: @@ -5435,6 +5437,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type required: @@ -6944,6 +6948,8 @@ spec: enum: - ephemeral - persistent-claim + volumeAttributesClass: + type: string required: - type config: diff --git a/packaging/install/cluster-operator/045-Crd-kafkanodepool.yaml b/packaging/install/cluster-operator/045-Crd-kafkanodepool.yaml index 3ed9f28dfea..1a2cffc5a4b 100644 --- a/packaging/install/cluster-operator/045-Crd-kafkanodepool.yaml +++ b/packaging/install/cluster-operator/045-Crd-kafkanodepool.yaml @@ -98,6 +98,9 @@ spec: - persistent-claim - jbod description: "Storage type, must be either 'ephemeral', 'persistent-claim', or 'jbod'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. volumes: type: array items: @@ -136,6 +139,9 @@ spec: - ephemeral - persistent-claim description: "Storage type, must be either 'ephemeral' or 'persistent-claim'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. required: - type description: List of volumes as Storage objects representing the JBOD disks array. @@ -1385,6 +1391,9 @@ spec: - persistent-claim - jbod description: "Storage type, must be either 'ephemeral', 'persistent-claim', or 'jbod'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. volumes: type: array items: @@ -1435,6 +1444,9 @@ spec: - ephemeral - persistent-claim description: "Storage type, must be either 'ephemeral' or 'persistent-claim'." + volumeAttributesClass: + type: string + description: Specifies `VolumeAttributeClass` name for dynamically configuring storage attributes. required: - type description: List of volumes as Storage objects representing the JBOD disks array.