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
6 changes: 6 additions & 0 deletions charts/postgres-operator/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ rules:
- delete
- get
- list
{{- if toString .Values.configKubernetes.storage_resize_mode | eq "pvc" }}
- patch
- update
{{- end }}
# to read existing PVs. Creation should be done via dynamic provisioning
- apiGroups:
- ""
Expand All @@ -113,7 +117,9 @@ rules:
verbs:
- get
- list
{{- if toString .Values.configKubernetes.storage_resize_mode | eq "ebs" }}
- update # only for resizing AWS volumes
{{- end }}
# to watch Spilo pods and do rolling updates. Creation via StatefulSet
- apiGroups:
- ""
Expand Down
2 changes: 1 addition & 1 deletion charts/postgres-operator/values-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ configKubernetes:
# whether the Spilo container should run in privileged mode
spilo_privileged: false
# storage resize strategy, available options are: ebs, pvc, off
storage_resize_mode: ebs
storage_resize_mode: pvc
# operator watches for postgres objects in the given namespace
watched_namespace: "*" # listen to all namespaces

Expand Down
2 changes: 1 addition & 1 deletion charts/postgres-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ configKubernetes:
# whether the Spilo container should run in privileged mode
spilo_privileged: "false"
# storage resize strategy, available options are: ebs, pvc, off
storage_resize_mode: ebs
storage_resize_mode: pvc
# operator watches for postgres objects in the given namespace
watched_namespace: "*" # listen to all namespaces

Expand Down
2 changes: 1 addition & 1 deletion manifests/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ data:
# spilo_runasgroup: 103
# spilo_fsgroup: 103
spilo_privileged: "false"
# storage_resize_mode: "off"
storage_resize_mode: "pvc"
super_username: postgres
# team_admin_role: "admin"
# team_api_role_configuration: "log_statement:all"
Expand Down
2 changes: 2 additions & 0 deletions manifests/operator-service-account-rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ rules:
- delete
- get
- list
- patch
- update
# to read existing PVs. Creation should be done via dynamic provisioning
- apiGroups:
- ""
Expand Down
2 changes: 1 addition & 1 deletion manifests/postgresql-operator-default-configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ configuration:
# spilo_runasgroup: 103
# spilo_fsgroup: 103
spilo_privileged: false
storage_resize_mode: ebs
storage_resize_mode: pvc
# toleration: {}
# watched_namespace: ""
postgres_pod_resources:
Expand Down
20 changes: 14 additions & 6 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec acidv1.Postgres
Secrets: make(map[types.UID]*v1.Secret),
Services: make(map[PostgresRole]*v1.Service),
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
userSyncStrategy: users.DefaultUserSyncStrategy{password_encryption},
userSyncStrategy: users.DefaultUserSyncStrategy{PasswordEncryption: password_encryption},
deleteOptions: metav1.DeleteOptions{PropagationPolicy: &deletePropagationPolicy},
podEventsQueue: podEventsQueue,
KubeClient: kubeClient,
Expand Down Expand Up @@ -671,13 +671,21 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {

// Volume
if oldSpec.Spec.Size != newSpec.Spec.Size {
c.logger.Debugf("syncing persistent volumes")
c.logVolumeChanges(oldSpec.Spec.Volume, newSpec.Spec.Volume)

if err := c.syncVolumes(); err != nil {
c.logger.Errorf("could not sync persistent volumes: %v", err)
updateFailed = true
c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode)
if c.OpConfig.StorageResizeMode == "pvc" {
if err := c.syncVolumeClaims(); err != nil {
c.logger.Errorf("could not sync persistent volume claims: %v", err)
updateFailed = true
}
} else if c.OpConfig.StorageResizeMode == "ebs" {
if err := c.syncVolumes(); err != nil {
c.logger.Errorf("could not sync persistent volumes: %v", err)
updateFailed = true
}
}
} else {
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.")
}

// Statefulset
Expand Down
3 changes: 1 addition & 2 deletions pkg/cluster/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
return err
}

c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode)
if c.OpConfig.StorageResizeMode == "pvc" {
c.logger.Debugf("syncing persistent volume claims")
if err = c.syncVolumeClaims(); err != nil {
err = fmt.Errorf("could not sync persistent volume claims: %v", err)
return err
Expand All @@ -70,7 +70,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
// TODO: handle the case of the cluster that is downsized and enlarged again
// (there will be a volume from the old pod for which we can't act before the
// the statefulset modification is concluded)
c.logger.Debugf("syncing persistent volumes")
if err = c.syncVolumes(); err != nil {
err = fmt.Errorf("could not sync persistent volumes: %v", err)
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/cluster/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (c *Cluster) resizeVolumeClaims(newVolume acidv1.Volume) error {
if err != nil {
return fmt.Errorf("could not parse volume size: %v", err)
}
_, newSize, err := c.listVolumesWithManifestSize(newVolume)
newSize := quantityToGigabyte(newQuantity)
for _, pvc := range pvcs {
volumeSize := quantityToGigabyte(pvc.Spec.Resources.Requests[v1.ResourceStorage])
if volumeSize >= newSize {
Expand Down
171 changes: 171 additions & 0 deletions pkg/cluster/volumes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package cluster

import (
"testing"

"context"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"

"github.com/stretchr/testify/assert"
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
"github.com/zalando/postgres-operator/pkg/util/config"
"github.com/zalando/postgres-operator/pkg/util/constants"
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
"k8s.io/client-go/kubernetes/fake"
)

func NewFakeKubernetesClient() (k8sutil.KubernetesClient, *fake.Clientset) {
clientSet := fake.NewSimpleClientset()

return k8sutil.KubernetesClient{
PersistentVolumeClaimsGetter: clientSet.CoreV1(),
}, clientSet
}

func TestResizeVolumeClaim(t *testing.T) {
testName := "test resizing of persistent volume claims"
client, _ := NewFakeKubernetesClient()
clusterName := "acid-test-cluster"
namespace := "default"
newVolumeSize := "2Gi"

// new cluster with pvc storage resize mode and configured labels
var cluster = New(
Config{
OpConfig: config.Config{
Resources: config.Resources{
ClusterLabels: map[string]string{"application": "spilo"},
ClusterNameLabel: "cluster-name",
},
StorageResizeMode: "pvc",
},
}, client, acidv1.Postgresql{}, logger, eventRecorder)

// set metadata, so that labels will get correct values
cluster.Name = clusterName
cluster.Namespace = namespace
filterLabels := cluster.labelsSet(false)

// define and create PVCs for 1Gi volumes
storage1Gi, err := resource.ParseQuantity("1Gi")
assert.NoError(t, err)

pvcList := &v1.PersistentVolumeClaimList{
Items: []v1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DataVolumeName + "-" + clusterName + "-0",
Namespace: namespace,
Labels: filterLabels,
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: storage1Gi,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DataVolumeName + "-" + clusterName + "-1",
Namespace: namespace,
Labels: filterLabels,
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: storage1Gi,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DataVolumeName + "-" + clusterName + "-2-0",
Namespace: namespace,
Labels: labels.Set{},
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: storage1Gi,
},
},
},
},
},
}

for _, pvc := range pvcList.Items {
cluster.KubeClient.PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{})
}

// test resizing
cluster.resizeVolumeClaims(acidv1.Volume{Size: newVolumeSize})

pvcs, err := cluster.listPersistentVolumeClaims()
assert.NoError(t, err)

// check if listPersistentVolumeClaims returns only the PVCs matching the filter
if len(pvcs) != len(pvcList.Items)-1 {
t.Errorf("%s: could not find all PVCs, got %v, expected %v", testName, len(pvcs), len(pvcList.Items)-1)
}

// check if PVCs were correctly resized
for _, pvc := range pvcs {
newStorageSize := quantityToGigabyte(pvc.Spec.Resources.Requests[v1.ResourceStorage])
expectedQuantity, err := resource.ParseQuantity(newVolumeSize)
assert.NoError(t, err)
expectedSize := quantityToGigabyte(expectedQuantity)
if newStorageSize != expectedSize {
t.Errorf("%s: resizing failed, got %v, expected %v", testName, newStorageSize, expectedSize)
}
}

// check if other PVC was not resized
pvc2, err := cluster.KubeClient.PersistentVolumeClaims(namespace).Get(context.TODO(), constants.DataVolumeName+"-"+clusterName+"-2-0", metav1.GetOptions{})
assert.NoError(t, err)
unchangedSize := quantityToGigabyte(pvc2.Spec.Resources.Requests[v1.ResourceStorage])
expectedSize := quantityToGigabyte(storage1Gi)
if unchangedSize != expectedSize {
t.Errorf("%s: volume size changed, got %v, expected %v", testName, unchangedSize, expectedSize)
}
}

func TestQuantityToGigabyte(t *testing.T) {
tests := []struct {
name string
quantityStr string
expected int64
}{
{
"test with 1Gi",
"1Gi",
1,
},
{
"test with float",
"1.5Gi",
int64(1),
},
{
"test with 1000Mi",
"1000Mi",
int64(0),
},
}

for _, tt := range tests {
quantity, err := resource.ParseQuantity(tt.quantityStr)
assert.NoError(t, err)
gigabyte := quantityToGigabyte(quantity)
if gigabyte != tt.expected {
t.Errorf("%s: got %v, expected %v", tt.name, gigabyte, tt.expected)
}
}
}