Skip to content

Commit

Permalink
Add support for CSI volumes in templates (#10891)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Scholz <www@scholzj.com>
  • Loading branch information
scholzj authored Dec 2, 2024
1 parent 03e3045 commit 923d00b
Show file tree
Hide file tree
Showing 18 changed files with 630 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
This change also means the restart reason ClientCaCertKeyReplaced is removed and either CaCertRenewed or CaCertHasOldGeneration will be used.
* Allow rolling update for new cluster CA trust (during Cluster CA key replacement) to continue where it left off before interruption without rolling all pods again.
* Update HTTP bridge to 0.31.0
* Add support for mounting CSI volumes using the `template` sections

### Major changes, deprecations and removals

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.fabric8.kubernetes.api.model.CSIVolumeSource;
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource;
import io.fabric8.kubernetes.api.model.EmptyDirVolumeSource;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
Expand All @@ -27,13 +28,14 @@
*/
@Buildable(editableEnabled = false, builderPackage = Constants.FABRIC8_KUBERNETES_API)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({ "name", "secret", "configMap", "emptyDir", "persistentVolumeClaim" })
@JsonPropertyOrder({ "name", "secret", "configMap", "emptyDir", "persistentVolumeClaim", "csi" })
@OneOf({
@OneOf.Alternative({
@OneOf.Alternative.Property(value = "secret", required = false),
@OneOf.Alternative.Property(value = "configMap", required = false),
@OneOf.Alternative.Property(value = "emptyDir", required = false),
@OneOf.Alternative.Property(value = "persistentVolumeClaim", required = false)
@OneOf.Alternative.Property(value = "persistentVolumeClaim", required = false),
@OneOf.Alternative.Property(value = "csi", required = false)
})
})
@EqualsAndHashCode
Expand All @@ -44,6 +46,7 @@ public class AdditionalVolume implements UnknownPropertyPreserving {
private ConfigMapVolumeSource configMap;
private EmptyDirVolumeSource emptyDir;
private PersistentVolumeClaimVolumeSource persistentVolumeClaim;
private CSIVolumeSource csi;
private Map<String, Object> additionalProperties = new HashMap<>(0);

@Description("Name to use for the volume. Required.")
Expand Down Expand Up @@ -100,6 +103,17 @@ public void setPersistentVolumeClaim(PersistentVolumeClaimVolumeSource persisten
this.persistentVolumeClaim = persistentVolumeClaim;
}

@Description("CSIVolumeSource object to use to populate the volume.")
@KubeLink(group = "core", version = "v1", kind = "csivolumesource")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public CSIVolumeSource getCsi() {
return csi;
}

public void setCsi(CSIVolumeSource csi) {
this.csi = csi;
}

@Override
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ public static void addAdditionalVolumeMounts(List<VolumeMount> volumeMounts, Con
private static Volume createVolumeFromConfig(AdditionalVolume volumeConfig) {
VolumeBuilder volumeBuilder = new VolumeBuilder().withName(volumeConfig.getName());
if (volumeConfig.getConfigMap() != null) {
volumeBuilder.withNewConfigMap().withName(volumeConfig.getConfigMap().getName()).endConfigMap();
volumeBuilder.withConfigMap(volumeConfig.getConfigMap());
} else if (volumeConfig.getSecret() != null) {
volumeBuilder.withNewSecret().withSecretName(volumeConfig.getSecret().getSecretName()).endSecret();
volumeBuilder.withSecret(volumeConfig.getSecret());
} else if (volumeConfig.getEmptyDir() != null) {
volumeBuilder.withNewEmptyDir().withMedium(volumeConfig.getEmptyDir().getMedium()).endEmptyDir();
volumeBuilder.withEmptyDir(volumeConfig.getEmptyDir());
} else if (volumeConfig.getPersistentVolumeClaim() != null) {
volumeBuilder.withPersistentVolumeClaim(volumeConfig.getPersistentVolumeClaim());
} else if (volumeConfig.getCsi() != null) {
volumeBuilder.withCsi(volumeConfig.getCsi());
}

return volumeBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
*/
package io.strimzi.operator.cluster.model;

import io.fabric8.kubernetes.api.model.CSIVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.EmptyDirVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
Expand Down Expand Up @@ -78,9 +84,7 @@ public void testAddAdditionalVolumes_InvalidNames() {
existingVolumes.add(new VolumeBuilder().withName("invalid_name!").build());
PodTemplate templatePod = new PodTemplateBuilder().withVolumes(new ArrayList<>()).build();

InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> {
TemplateUtils.addAdditionalVolumes(templatePod, existingVolumes);
});
InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> TemplateUtils.addAdditionalVolumes(templatePod, existingVolumes));

assertThat(exception.getMessage(), is("Volume names [invalid_name!] are invalid and do not match the pattern " + VOLUME_NAME_REGEX));
}
Expand All @@ -93,9 +97,7 @@ public void testAddAdditionalVolumes_DuplicateNames() {
additionalVolumes.add(new AdditionalVolumeBuilder().withName("duplicate").build());
PodTemplate templatePod = new PodTemplateBuilder().withVolumes(additionalVolumes).build();

InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> {
TemplateUtils.addAdditionalVolumes(templatePod, existingVolumes);
});
InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> TemplateUtils.addAdditionalVolumes(templatePod, existingVolumes));

assertThat(exception.getMessage(), is("Duplicate volume names found in additional volumes: [duplicate]"));
}
Expand All @@ -104,19 +106,77 @@ public void testAddAdditionalVolumes_DuplicateNames() {
public void testAddAdditionalVolumes_ValidInputs() {
List<Volume> existingVolumes = new ArrayList<>();
existingVolumes.add(new VolumeBuilder().withName("existingVolume1").build());
List<AdditionalVolume> additionalVolumes = new ArrayList<>();
additionalVolumes.add(new AdditionalVolumeBuilder().withName("newVolume1").build());
additionalVolumes.add(new AdditionalVolumeBuilder().withName("newVolume2").build());
PodTemplate templatePod = new PodTemplateBuilder().withVolumes(additionalVolumes).build();

PodTemplate templatePod = new PodTemplateBuilder()
.withVolumes(
new AdditionalVolumeBuilder()
.withName("secret-volume")
.withSecret(new SecretVolumeSourceBuilder().withSecretName("my-secret").build())
.build(),
new AdditionalVolumeBuilder()
.withName("cm-volume")
.withConfigMap(new ConfigMapVolumeSourceBuilder().withName("my-cm").build())
.build(),
new AdditionalVolumeBuilder()
.withName("empty-dir-volume")
.withEmptyDir(new EmptyDirVolumeSourceBuilder().withMedium("Memory").withSizeLimit(new Quantity("100Mi")).build())
.build(),
new AdditionalVolumeBuilder()
.withName("pvc-volume")
.withPersistentVolumeClaim(new PersistentVolumeClaimVolumeSourceBuilder().withClaimName("my-pvc").build())
.build(),
new AdditionalVolumeBuilder()
.withName("csi-volume")
.withCsi(new CSIVolumeSourceBuilder().withDriver("csi.cert-manager.io").withReadOnly().withVolumeAttributes(Map.of("csi.cert-manager.io/issuer-name", "my-ca", "csi.cert-manager.io/dns-names", "${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local")).build())
.build())
.build();

TemplateUtils.addAdditionalVolumes(templatePod, existingVolumes);

assertThat(existingVolumes.size(), is(3));
assertThat(existingVolumes.get(1).getName(), is("newVolume1"));
assertThat(existingVolumes.get(2).getName(), is("newVolume2"));
assertThat(existingVolumes.size(), is(6));

assertThat(existingVolumes.get(0).getName(), is("existingVolume1"));

assertThat(existingVolumes.get(1).getName(), is("secret-volume"));
assertThat(existingVolumes.get(1).getSecret().getSecretName(), is("my-secret"));
assertThat(existingVolumes.get(1).getConfigMap(), is(nullValue()));
assertThat(existingVolumes.get(1).getEmptyDir(), is(nullValue()));
assertThat(existingVolumes.get(1).getPersistentVolumeClaim(), is(nullValue()));
assertThat(existingVolumes.get(1).getCsi(), is(nullValue()));

assertThat(existingVolumes.get(2).getName(), is("cm-volume"));
assertThat(existingVolumes.get(2).getConfigMap().getName(), is("my-cm"));
assertThat(existingVolumes.get(2).getSecret(), is(nullValue()));
assertThat(existingVolumes.get(2).getEmptyDir(), is(nullValue()));
assertThat(existingVolumes.get(2).getPersistentVolumeClaim(), is(nullValue()));
assertThat(existingVolumes.get(2).getCsi(), is(nullValue()));

assertThat(existingVolumes.get(3).getName(), is("empty-dir-volume"));
assertThat(existingVolumes.get(3).getEmptyDir().getMedium(), is("Memory"));
assertThat(existingVolumes.get(3).getEmptyDir().getSizeLimit(), is(new Quantity("100Mi")));
assertThat(existingVolumes.get(3).getSecret(), is(nullValue()));
assertThat(existingVolumes.get(3).getConfigMap(), is(nullValue()));
assertThat(existingVolumes.get(3).getPersistentVolumeClaim(), is(nullValue()));
assertThat(existingVolumes.get(3).getCsi(), is(nullValue()));

assertThat(existingVolumes.get(4).getName(), is("pvc-volume"));
assertThat(existingVolumes.get(4).getPersistentVolumeClaim().getClaimName(), is("my-pvc"));
assertThat(existingVolumes.get(4).getSecret(), is(nullValue()));
assertThat(existingVolumes.get(4).getConfigMap(), is(nullValue()));
assertThat(existingVolumes.get(4).getEmptyDir(), is(nullValue()));
assertThat(existingVolumes.get(4).getCsi(), is(nullValue()));

assertThat(existingVolumes.get(5).getName(), is("csi-volume"));
assertThat(existingVolumes.get(5).getCsi().getDriver(), is("csi.cert-manager.io"));
assertThat(existingVolumes.get(5).getCsi().getReadOnly(), is(true));
assertThat(existingVolumes.get(5).getCsi().getVolumeAttributes().get("csi.cert-manager.io/issuer-name"), is("my-ca"));
assertThat(existingVolumes.get(5).getCsi().getVolumeAttributes().get("csi.cert-manager.io/dns-names"), is("${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local"));
assertThat(existingVolumes.get(5).getSecret(), is(nullValue()));
assertThat(existingVolumes.get(5).getConfigMap(), is(nullValue()));
assertThat(existingVolumes.get(5).getEmptyDir(), is(nullValue()));
assertThat(existingVolumes.get(5).getPersistentVolumeClaim(), is(nullValue()));
}


@ParallelTest
public void testAddAdditionalVolumeMounts_ValidInputs() {
List<VolumeMount> volumeMounts = new ArrayList<>();
Expand All @@ -142,14 +202,8 @@ public void testAddAdditionalVolumeMounts_InvalidInputs() {
ContainerTemplate containerTemplate = new ContainerTemplate();
containerTemplate.setVolumeMounts(additionalVolumeMounts);

InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> {
TemplateUtils.addAdditionalVolumeMounts(volumeMounts, containerTemplate);
});
InvalidResourceException exception = assertThrows(InvalidResourceException.class, () -> TemplateUtils.addAdditionalVolumeMounts(volumeMounts, containerTemplate));

assertThat(exception.getMessage(), is(String.format("Forbidden path found in additional volumes. Should start with %s", ALLOWED_MOUNT_PATH)));
}




}
3 changes: 3 additions & 0 deletions documentation/modules/appendix_crds.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,9 @@ Used in: xref:type-PodTemplate-{context}[`PodTemplate`]
|persistentVolumeClaim
|https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#persistentvolumeclaimvolumesource-v1-core[PersistentVolumeClaimVolumeSource]
|PersistentVolumeClaim object to use to populate the volume.
|csi
|https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#csivolumesource-v1-core[CSIVolumeSource]
|CSIVolumeSource object to use to populate the volume.
|====

[id='type-InternalServiceTemplate-{context}']
Expand Down
10 changes: 10 additions & 0 deletions documentation/modules/con-common-configuration-properties.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ Supported Volume Types
* ConfigMap
* EmptyDir
* PersistentVolumeClaims
* CSI Volumes

.Example configuration for additional volumes
[source,yaml,subs=attributes+]
Expand All @@ -714,6 +715,13 @@ spec:
- name: example-pvc-volume
persistentVolumeClaim:
claimName: myclaim
- name: example-csi-volume
csi:
driver: csi.cert-manager.io
readOnly: true
volumeAttributes:
csi.cert-manager.io/issuer-name: my-ca
csi.cert-manager.io/dns-names: ${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local
kafkaContainer:
volumeMounts:
- name: example-secret
Expand All @@ -724,6 +732,8 @@ spec:
mountPath: /mnt/temp
- name: example-pvc-volume
mountPath: /mnt/data
- name: example-csi-volume
mountPath: /mnt/certificate
----

You can use volumes to store files containing configuration values for a Kafka component and then load those values using a configuration provider.
Expand Down
Loading

0 comments on commit 923d00b

Please sign in to comment.