Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ type WorkspaceConfig struct {
// DefaultStorageSize defines an optional struct with fields to specify the sizes of Persistent Volume Claims for storage
// classes used by DevWorkspaces.
DefaultStorageSize *StorageSizes `json:"defaultStorageSize,omitempty"`
// StorageAccessMode are the desired access modes the volume should have. It defaults
// to ReadWriteOnce if not specified
StorageAccessMode []corev1.PersistentVolumeAccessMode `json:"storageAccessMode,omitempty"`
// PersistUserHome defines configuration options for persisting the `/home/user/`
// directory in workspaces.
PersistUserHome *PersistentHomeConfig `json:"persistUserHome,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions apis/controller/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions deploy/deployment/kubernetes/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions deploy/deployment/openshift/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
if from.Workspace.ContainerSecurityContext != nil {
to.Workspace.ContainerSecurityContext = mergeContainerSecurityContext(to.Workspace.ContainerSecurityContext, from.Workspace.ContainerSecurityContext)
}
if from.Workspace.StorageAccessMode != nil {
to.Workspace.StorageAccessMode = from.Workspace.StorageAccessMode
}
if from.Workspace.DefaultStorageSize != nil {
if to.Workspace.DefaultStorageSize == nil {
to.Workspace.DefaultStorageSize = &controller.StorageSizes{}
Expand Down
18 changes: 18 additions & 0 deletions pkg/config/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,24 @@ func TestMergeConfigLooksAtAllFields(t *testing.T) {
assert.Equal(t, expectedConfig, actualConfig, "merging configs should merge all fields")
}

func TestMergeConfigMergesStorageAccessMode(t *testing.T) {
// Given
expectedConfig := &v1alpha1.OperatorConfiguration{
Workspace: &v1alpha1.WorkspaceConfig{
StorageAccessMode: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteMany,
},
},
}
actualConfig := &v1alpha1.OperatorConfiguration{}

// When
mergeConfig(expectedConfig, actualConfig)

// Then
assert.Equal(t, expectedConfig.Workspace.StorageAccessMode, actualConfig.Workspace.StorageAccessMode)
}

func fuzzQuantity(q *resource.Quantity, c fuzz.Continue) {
q.Set(c.Int63n(999))
q.Format = resource.DecimalSI
Expand Down
8 changes: 4 additions & 4 deletions pkg/provision/storage/commonStorage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ func TestUseCommonStorageProvisionerForPerUserStorageClass(t *testing.T) {
func TestProvisionStorageForCommonStorageClass(t *testing.T) {
tests := loadAllTestCasesOrPanic(t, "testdata/common-storage")
commonStorage := CommonStorageProvisioner{}
commonPVC, err := getPVCSpec("claim-devworkspace", "test-namespace", nil, resource.MustParse("10Gi"))
commonPVC, err := getPVCSpec("claim-devworkspace", "test-namespace", nil, resource.MustParse("10Gi"), nil)
if err != nil {
t.Fatalf("Failure during setup: %s", err)
}
commonPVC.Status.Phase = corev1.ClaimBound
clusterAPI := sync.ClusterAPI{
Client: fake.NewFakeClientWithScheme(scheme, commonPVC),
Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(commonPVC).Build(),
Logger: zap.New(),
}

Expand Down Expand Up @@ -166,15 +166,15 @@ func TestProvisionStorageForCommonStorageClass(t *testing.T) {

func TestTerminatingPVC(t *testing.T) {
commonStorage := CommonStorageProvisioner{}
commonPVC, err := getPVCSpec("claim-devworkspace", "test-namespace", nil, resource.MustParse("10Gi"))
commonPVC, err := getPVCSpec("claim-devworkspace", "test-namespace", nil, resource.MustParse("10Gi"), nil)
if err != nil {
t.Fatalf("Failure during setup: %s", err)
}
testTime := metav1.Now()
commonPVC.SetDeletionTimestamp(&testTime)

clusterAPI := sync.ClusterAPI{
Client: fake.NewFakeClientWithScheme(scheme, commonPVC),
Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(commonPVC).Build(),
Logger: zap.New(),
}
testCase := loadTestCaseOrPanic(t, "testdata/common-storage/rewrites-volumes-for-common-pvc-strategy.yaml")
Expand Down
2 changes: 1 addition & 1 deletion pkg/provision/storage/perWorkspaceStorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func syncPerWorkspacePVC(workspace *common.DevWorkspaceWithConfig, clusterAPI sy
}

storageClass := workspace.Config.Workspace.StorageClassName
pvc, err := getPVCSpec(common.PerWorkspacePVCName(workspace.Status.DevWorkspaceId), workspace.Namespace, storageClass, *pvcSize)
pvc, err := getPVCSpec(common.PerWorkspacePVCName(workspace.Status.DevWorkspaceId), workspace.Namespace, storageClass, *pvcSize, workspace.Config.Workspace.StorageAccessMode)
if err != nil {
return nil, err
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/provision/storage/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,18 @@ func WorkspaceNeedsStorage(workspace *dw.DevWorkspaceTemplateSpec) bool {
return containerlib.AnyMountSources(workspace.Components)
}

func getPVCSpec(name, namespace string, storageClass *string, size resource.Quantity) (*corev1.PersistentVolumeClaim, error) {
func getPVCSpec(name, namespace string, storageClass *string, size resource.Quantity, storageAccessMode []corev1.PersistentVolumeAccessMode) (*corev1.PersistentVolumeClaim, error) {
if storageAccessMode == nil || len(storageAccessMode) == 0 {
storageAccessMode = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}
}

return &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
AccessModes: storageAccessMode,
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"storage": size,
Expand Down Expand Up @@ -99,7 +100,7 @@ func syncCommonPVC(namespace string, config *v1alpha1.OperatorConfiguration, clu
}
}

pvc, err := getPVCSpec(config.Workspace.PVCName, namespace, config.Workspace.StorageClassName, pvcSize)
pvc, err := getPVCSpec(config.Workspace.PVCName, namespace, config.Workspace.StorageClassName, pvcSize, config.Workspace.StorageAccessMode)
if err != nil {
return nil, err
}
Expand Down
61 changes: 61 additions & 0 deletions pkg/provision/storage/shared_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Copyright (c) 2019-2025 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package storage

import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)

func TestGetPVCSpecWithDefaultStorageAccessMode(t *testing.T) {
// Given
name := "test-pvc"
namespace := "default"
storageClass := "standard"

// When
pvc, err := getPVCSpec(name, namespace, &storageClass, resource.MustParse("5Gi"), nil)

// Then
assert.NoError(t, err, "Expected no error")
assert.Equal(t, name, pvc.Name, "PVC name should match")
assert.Equal(t, namespace, pvc.Namespace, "PVC namespace should match")
assert.Equal(t, storageClass, *pvc.Spec.StorageClassName, "Storage class should match")
assert.Equal(t, []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, pvc.Spec.AccessModes, "Access modes should match")
assert.Equal(t, "5Gi", pvc.Spec.Resources.Requests.Storage().String(), "Storage size should match")
}

func TestGetPVCSpecWithCustomStorageAccessMode(t *testing.T) {
// Given
name := "test-pvc"
namespace := "default"
storageClass := "standard"
storageAccessMode := []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOncePod}

// When
pvc, err := getPVCSpec(name, namespace, &storageClass, resource.MustParse("5Gi"), storageAccessMode)

// Then
assert.NoError(t, err, "Expected no error")
assert.Equal(t, name, pvc.Name, "PVC name should match")
assert.Equal(t, namespace, pvc.Namespace, "PVC namespace should match")
assert.Equal(t, storageClass, *pvc.Spec.StorageClassName, "Storage class should match")
assert.Equal(t, []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOncePod}, pvc.Spec.AccessModes, "Access modes should match")
assert.Equal(t, "5Gi", pvc.Spec.Resources.Requests.Storage().String(), "Storage size should match")
}
Loading