Files
portainer/api/kubernetes/cli/storage_classes_test.go
Steven Kang 5b9976433f feat(k8s): Refactor Volumes page (#2510)
Co-authored-by: Nicholas Loomans <nicholas.loomans@portainer.io>
Co-authored-by: Robbie Cowan <robert.cowan@portainer.io>
Co-authored-by: RHCowan <50324595+RHCowan@users.noreply.github.com>
2026-05-19 10:39:24 +12:00

247 lines
6.5 KiB
Go

package cli
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
storagev1 "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kfake "k8s.io/client-go/kubernetes/fake"
)
func TestParseStorageClassDetail_IsDefault(t *testing.T) {
t.Parallel()
t.Run("annotation true sets isDefault", func(t *testing.T) {
t.Parallel()
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "fast",
Annotations: map[string]string{
storageClassDefaultAnnotation: "true",
},
},
}
result := parseStorageClassDetail(sc)
assert.True(t, result.IsDefault)
})
t.Run("annotation false gives isDefault=false", func(t *testing.T) {
t.Parallel()
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "slow",
Annotations: map[string]string{
storageClassDefaultAnnotation: "false",
},
},
}
result := parseStorageClassDetail(sc)
assert.False(t, result.IsDefault)
})
t.Run("missing annotation gives isDefault=false", func(t *testing.T) {
t.Parallel()
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{Name: "no-annotation"},
}
result := parseStorageClassDetail(sc)
assert.False(t, result.IsDefault)
})
}
func TestSetDefaultStorageClass(t *testing.T) {
t.Parallel()
t.Run("sets default annotation on target SC", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{Name: "standard"},
}
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), sc, metav1.CreateOptions{})
require.NoError(t, err)
err = k.SetDefaultStorageClass("standard")
require.NoError(t, err)
result, err := k.GetStorageClass("standard")
require.NoError(t, err)
assert.True(t, result.IsDefault)
})
t.Run("removes default annotation from previously-default SC", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
oldDefault := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "old-default",
Annotations: map[string]string{
storageClassDefaultAnnotation: "true",
},
},
}
newDefault := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{Name: "new-default"},
}
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), oldDefault, metav1.CreateOptions{})
require.NoError(t, err)
_, err = k.cli.StorageV1().StorageClasses().Create(t.Context(), newDefault, metav1.CreateOptions{})
require.NoError(t, err)
err = k.SetDefaultStorageClass("new-default")
require.NoError(t, err)
old, err := k.GetStorageClass("old-default")
require.NoError(t, err)
assert.False(t, old.IsDefault)
newSC, err := k.GetStorageClass("new-default")
require.NoError(t, err)
assert.True(t, newSC.IsDefault)
})
t.Run("no-op if target is already default", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "already-default",
Annotations: map[string]string{
storageClassDefaultAnnotation: "true",
},
},
}
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), sc, metav1.CreateOptions{})
require.NoError(t, err)
err = k.SetDefaultStorageClass("already-default")
require.NoError(t, err)
result, err := k.GetStorageClass("already-default")
require.NoError(t, err)
assert.True(t, result.IsDefault)
})
t.Run("handles multiple SCs where only one was previously default", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
scs := []*storagev1.StorageClass{
{
ObjectMeta: metav1.ObjectMeta{
Name: "sc-default",
Annotations: map[string]string{
storageClassDefaultAnnotation: "true",
},
},
},
{ObjectMeta: metav1.ObjectMeta{Name: "sc-other-1"}},
{ObjectMeta: metav1.ObjectMeta{Name: "sc-other-2"}},
{ObjectMeta: metav1.ObjectMeta{Name: "sc-target"}},
}
for _, sc := range scs {
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), sc, metav1.CreateOptions{})
require.NoError(t, err)
}
err := k.SetDefaultStorageClass("sc-target")
require.NoError(t, err)
target, err := k.GetStorageClass("sc-target")
require.NoError(t, err)
assert.True(t, target.IsDefault)
oldDefault, err := k.GetStorageClass("sc-default")
require.NoError(t, err)
assert.False(t, oldDefault.IsDefault)
for _, name := range []string{"sc-other-1", "sc-other-2"} {
sc, err := k.GetStorageClass(name)
require.NoError(t, err)
assert.False(t, sc.IsDefault)
}
})
}
func TestGetStorageClasses(t *testing.T) {
t.Parallel()
t.Run("empty cluster returns empty slice", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
scs, err := k.GetStorageClasses()
require.NoError(t, err)
assert.Empty(t, scs)
})
t.Run("returns all SCs with correct fields", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
allowExpansion := true
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "fast",
Annotations: map[string]string{
storageClassDefaultAnnotation: "true",
},
},
Provisioner: "kubernetes.io/aws-ebs",
AllowVolumeExpansion: &allowExpansion,
}
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), sc, metav1.CreateOptions{})
require.NoError(t, err)
scs, err := k.GetStorageClasses()
require.NoError(t, err)
require.Len(t, scs, 1)
got := scs[0]
assert.Equal(t, "fast", got.Name)
assert.Equal(t, "kubernetes.io/aws-ebs", got.Provisioner)
assert.True(t, got.IsDefault)
require.NotNil(t, got.AllowVolumeExpansion)
assert.True(t, *got.AllowVolumeExpansion)
})
}
func TestDeleteStorageClasses(t *testing.T) {
t.Parallel()
t.Run("deletes specified SCs", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
for _, name := range []string{"sc-a", "sc-b", "sc-c"} {
sc := &storagev1.StorageClass{
ObjectMeta: metav1.ObjectMeta{Name: name},
}
_, err := k.cli.StorageV1().StorageClasses().Create(t.Context(), sc, metav1.CreateOptions{})
require.NoError(t, err)
}
err := k.DeleteStorageClasses([]string{"sc-a", "sc-c"})
require.NoError(t, err)
scs, err := k.GetStorageClasses()
require.NoError(t, err)
require.Len(t, scs, 1)
assert.Equal(t, "sc-b", scs[0].Name)
})
t.Run("returns error for non-existent SC", func(t *testing.T) {
t.Parallel()
k := NewTestKubeClient(kfake.NewClientset())
err := k.DeleteStorageClasses([]string{"does-not-exist"})
assert.Error(t, err)
})
}