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
41 changes: 28 additions & 13 deletions api/v1alpha1/perses_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/conversion"
conv "sigs.k8s.io/controller-runtime/pkg/conversion"

Expand Down Expand Up @@ -80,27 +81,41 @@ func Convert_v1alpha2_OAuth_To_v1alpha1_OAuth(in *v1alpha2.OAuth, out *OAuth, s
return nil
}

// Convert_v1alpha1_StorageConfiguration_To_v1alpha2_StorageConfiguration converts StorageConfiguration from v1alpha1 to v1alpha2.
// Convert_v1alpha1_StorageConfiguration_To_v1alpha2_StorageConfiguration converts v1alpha1 storage to v1alpha2.
// Migrates StorageClass and Size fields into PersistentVolumeClaimTemplate.
func Convert_v1alpha1_StorageConfiguration_To_v1alpha2_StorageConfiguration(in *StorageConfiguration, out *v1alpha2.StorageConfiguration, s conversion.Scope) error {
if err := autoConvert_v1alpha1_StorageConfiguration_To_v1alpha2_StorageConfiguration(in, out, s); err != nil {
return err
// Only create PVC template if v1alpha1 has storage config
if in.StorageClass == nil && in.Size.IsZero() {
return nil
}

out.PersistentVolumeClaimTemplate = &corev1.PersistentVolumeClaimSpec{
StorageClassName: in.StorageClass,
}
// Convert Size from resource.Quantity to *resource.Quantity

if !in.Size.IsZero() {
size := in.Size.DeepCopy()
out.Size = &size
out.PersistentVolumeClaimTemplate.Resources.Requests = corev1.ResourceList{
corev1.ResourceStorage: in.Size,
}
}

return nil
}

// Convert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration converts StorageConfiguration from v1alpha2 to v1alpha1.
// Convert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration converts v1alpha2 storage to v1alpha1.
// Extracts StorageClass and Size from PersistentVolumeClaimTemplate.
func Convert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration(in *v1alpha2.StorageConfiguration, out *StorageConfiguration, s conversion.Scope) error {
if err := autoConvert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration(in, out, s); err != nil {
return err
}
// Convert Size from *resource.Quantity to resource.Quantity
if in.Size != nil {
out.Size = *in.Size
// Extract from PVC template if it exists
if in.PersistentVolumeClaimTemplate != nil {
out.StorageClass = in.PersistentVolumeClaimTemplate.StorageClassName

if storage, ok := in.PersistentVolumeClaimTemplate.Resources.Requests[corev1.ResourceStorage]; ok {
out.Size = storage
}
}

// EmptyDir cannot be converted to v1alpha1 (not supported)
// It will be silently dropped

return nil
}
63 changes: 63 additions & 0 deletions api/v1alpha1/perses_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import (
"strings"
"testing"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/ptr"

"github.com/perses/perses-operator/api/v1alpha2"
)

Expand Down Expand Up @@ -72,3 +76,62 @@ func TestBasicAuthConversion(t *testing.T) {
t.Errorf("v1alpha1 should not use 'passwordPath', got: %s", string(v1JSONReverse))
}
}

func TestStorageConfigurationConversion(t *testing.T) {
storageClass := "fast-ssd"
size := resource.MustParse("10Gi")

// Test v1alpha1 -> v1alpha2 conversion
v1Storage := &StorageConfiguration{
StorageClass: &storageClass,
Size: size,
}

var v2Storage v1alpha2.StorageConfiguration
if err := Convert_v1alpha1_StorageConfiguration_To_v1alpha2_StorageConfiguration(v1Storage, &v2Storage, nil); err != nil {
t.Fatalf("conversion v1->v2: %v", err)
}

// Verify PVC template was created with correct values
if v2Storage.PersistentVolumeClaimTemplate == nil {
t.Fatal("expected PersistentVolumeClaimTemplate to be set")
}
if *v2Storage.PersistentVolumeClaimTemplate.StorageClassName != storageClass {
t.Errorf("expected storageClassName=%s, got %s", storageClass, *v2Storage.PersistentVolumeClaimTemplate.StorageClassName)
}

storageReq := v2Storage.PersistentVolumeClaimTemplate.Resources.Requests[corev1.ResourceStorage]
if !storageReq.Equal(size) {
t.Errorf("expected size=%s, got %s", size.String(), storageReq.String())
}

// Test v1alpha2 -> v1alpha1 conversion (round trip)
var v1StorageReverse StorageConfiguration
if err := Convert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration(&v2Storage, &v1StorageReverse, nil); err != nil {
t.Fatalf("conversion v2->v1: %v", err)
}

if v1StorageReverse.StorageClass == nil || *v1StorageReverse.StorageClass != storageClass {
t.Errorf("expected storageClass=%s after round trip", storageClass)
}
if !v1StorageReverse.Size.Equal(size) {
t.Errorf("expected size=%s after round trip, got %s", size.String(), v1StorageReverse.Size.String())
}

// Test v1alpha2 with EmptyDir (should drop EmptyDir when converting to v1alpha1)
v2StorageWithEmptyDir := v1alpha2.StorageConfiguration{
EmptyDir: &corev1.EmptyDirVolumeSource{
SizeLimit: ptr.To(resource.MustParse("1Gi")),
},
}

var v1StorageDropped StorageConfiguration
if err := Convert_v1alpha2_StorageConfiguration_To_v1alpha1_StorageConfiguration(&v2StorageWithEmptyDir, &v1StorageDropped, nil); err != nil {
t.Fatalf("conversion v2->v1 with emptyDir: %v", err)
}

// v1alpha1 should have no storage config since EmptyDir isn't supported
if v1StorageDropped.StorageClass != nil || !v1StorageDropped.Size.IsZero() {
t.Error("expected empty storage config when converting emptyDir from v2 to v1")
}
}
8 changes: 4 additions & 4 deletions api/v1alpha1/zz_generated.conversion.go

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

33 changes: 25 additions & 8 deletions api/v1alpha2/perses_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package v1alpha2

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -88,7 +87,6 @@ type PersesSpec struct {
TLS *TLS `json:"tls,omitempty"`
// Storage configuration used by the StatefulSet
// +operator-sdk:csv:customresourcedefinitions:type=spec
// +kubebuilder:default:={size: "1Gi"}
// +optional
Storage *StorageConfiguration `json:"storage,omitempty"`
// ServiceAccountName is the name of the ServiceAccount to use for the Perses deployment or statefulset
Expand Down Expand Up @@ -256,15 +254,18 @@ type Certificate struct {
}

// StorageConfiguration is the configuration used to create and reconcile PVCs
// +kubebuilder:validation:XValidation:rule="!(has(self.emptyDir) && has(self.pvcTemplate))",message="emptyDir and pvcTemplate are mutually exclusive"
type StorageConfiguration struct {
// StorageClass specifies the StorageClass to use for PersistentVolumeClaims
// If not specified, the default StorageClass will be used
// EmptyDir to use for ephemeral storage.
// When set, data will be lost when the pod is deleted or restarted.
// Mutually exclusive with PersistentVolumeClaimTemplate.
// +optional
StorageClass *string `json:"storageClass,omitempty"`
// Size specifies the storage capacity for the PersistentVolumeClaim
// Once set, the size cannot be decreased (only increased if the StorageClass supports volume expansion)
EmptyDir *corev1.EmptyDirVolumeSource `json:"emptyDir,omitempty"`

// PersistentVolumeClaimTemplate is the template for PVCs that will be created.
// Mutually exclusive with EmptyDir.
// +optional
Size *resource.Quantity `json:"size,omitempty"`
PersistentVolumeClaimTemplate *corev1.PersistentVolumeClaimSpec `json:"pvcTemplate,omitempty"`
}

// PersesStatus defines the observed state of Perses
Expand Down Expand Up @@ -295,6 +296,22 @@ type Perses struct {
Status PersesStatus `json:"status,omitempty"`
}

// RequiresDeployment returns true if the Perses instance should be deployed as a Deployment.
// This is the case when using SQL database OR file database with EmptyDir storage.
func (p *Perses) RequiresDeployment() bool {
usesSQLDatabase := p.Spec.Config.Database.SQL != nil
usesFileWithEmptyDir := p.Spec.Config.Database.File != nil &&
p.Spec.Storage != nil && p.Spec.Storage.EmptyDir != nil
return usesSQLDatabase || usesFileWithEmptyDir
}

// RequiresStatefulSet returns true if the Perses instance should be deployed as a StatefulSet.
// This is the case when using file database with persistent volume storage (not EmptyDir).
func (p *Perses) RequiresStatefulSet() bool {
return p.Spec.Config.Database.File != nil &&
(p.Spec.Storage == nil || p.Spec.Storage.EmptyDir == nil)
}

//+kubebuilder:object:root=true

// PersesList contains a list of Perses
Expand Down
16 changes: 8 additions & 8 deletions api/v1alpha2/zz_generated.deepcopy.go

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

Loading