Skip to content

Commit 44de2ac

Browse files
authored
Merge pull request #357 from arangodb/feature/resize-volumes
Volume resizing
2 parents ab9554f + e802597 commit 44de2ac

File tree

5 files changed

+159
-7
lines changed

5 files changed

+159
-7
lines changed

pkg/deployment/reconcile/plan_builder_storage.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ func createRotateServerStoragePlan(log zerolog.Logger, apiObject k8sutil.APIObje
5757
}
5858
groupSpec := spec.GetServerGroupSpec(group)
5959
storageClassName := groupSpec.GetStorageClassName()
60-
if storageClassName == "" {
61-
// Using default storage class name
62-
continue
63-
}
6460
// Load PVC
6561
pvc, err := getPVC(m.PersistentVolumeClaimName)
6662
if err != nil {
@@ -71,10 +67,17 @@ func createRotateServerStoragePlan(log zerolog.Logger, apiObject k8sutil.APIObje
7167
continue
7268
}
7369
replacementNeeded := false
74-
if util.StringOrDefault(pvc.Spec.StorageClassName) != storageClassName {
70+
if util.StringOrDefault(pvc.Spec.StorageClassName) != storageClassName && storageClassName != "" {
7571
// Storageclass has changed
72+
log.Debug().Str("pod-name", m.PodName).
73+
Str("pvc-storage-class", util.StringOrDefault(pvc.Spec.StorageClassName)).
74+
Str("group-storage-class", storageClassName).Msg("Storage class has changed - pod needs replacement")
7675
replacementNeeded = true
7776
}
77+
rotationNeeded := false
78+
if k8sutil.IsPersistentVolumeClaimFileSystemResizePending(pvc) {
79+
rotationNeeded = true
80+
}
7881
if replacementNeeded {
7982
if group != api.ServerGroupAgents && group != api.ServerGroupDBServers {
8083
// Only agents & dbservers are allowed to change their storage class.
@@ -107,6 +110,8 @@ func createRotateServerStoragePlan(log zerolog.Logger, apiObject k8sutil.APIObje
107110
)
108111
}
109112
}
113+
} else if rotationNeeded {
114+
plan = createRotateMemberPlan(log, m, group, "Filesystem resize pending")
110115
}
111116
}
112117
return nil

pkg/deployment/resources/pvc_inspector.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/arangodb/kube-arangodb/pkg/metrics"
3030
"github.com/arangodb/kube-arangodb/pkg/util"
3131
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
32+
apiv1 "k8s.io/api/core/v1"
3233
)
3334

3435
var (
@@ -57,6 +58,7 @@ func (r *Resources) InspectPVCs(ctx context.Context) (util.Interval, error) {
5758

5859
// Update member status from all pods found
5960
status, _ := r.context.GetStatus()
61+
spec := r.context.GetSpec()
6062
for _, p := range pvcs {
6163
// PVC belongs to this deployment, update metric
6264
inspectedPVCsCounters.WithLabelValues(deploymentName).Inc()
@@ -79,6 +81,30 @@ func (r *Resources) InspectPVCs(ctx context.Context) (util.Interval, error) {
7981
continue
8082
}
8183

84+
// Resize inspector
85+
groupSpec := spec.GetServerGroupSpec(group)
86+
if requestedSize, ok := groupSpec.Resources.Requests[apiv1.ResourceStorage]; ok {
87+
if volumeSize, ok := p.Spec.Resources.Requests[apiv1.ResourceStorage]; ok {
88+
cmp := volumeSize.Cmp(requestedSize)
89+
if cmp < 0 {
90+
// Size of the volume is smaller than the requested size
91+
// Update the pvc with the request size
92+
p.Spec.Resources.Requests[apiv1.ResourceStorage] = requestedSize
93+
94+
log.Debug().Str("pvc-capacity", volumeSize.String()).Str("requested", requestedSize.String()).Msg("PVC capacity differs - updating")
95+
kube := r.context.GetKubeCli()
96+
if _, err := kube.CoreV1().PersistentVolumeClaims(r.context.GetNamespace()).Update(&p); err != nil {
97+
log.Error().Err(err).Msg("Failed to update pvc")
98+
}
99+
r.context.CreateEvent(k8sutil.NewPVCResizedEvent(r.context.GetAPIObject(), p.Name))
100+
} else if cmp > 0 {
101+
log.Error().Str("server-group", group.AsRole()).Str("pvc-storage-size", volumeSize.String()).Str("requested-size", requestedSize.String()).
102+
Msg("Volume size should not shrink")
103+
r.context.CreateEvent(k8sutil.NewCannotShrinkVolumeEvent(r.context.GetAPIObject(), p.Name))
104+
}
105+
}
106+
}
107+
82108
if k8sutil.IsPersistentVolumeClaimMarkedForDeletion(&p) {
83109
// Process finalizers
84110
if x, err := r.runPVCFinalizers(ctx, &p, group, memberStatus); err != nil {

pkg/util/k8sutil/events.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,24 @@ func NewDowntimeNotAllowedEvent(apiObject APIObject, operation string) *Event {
193193
return event
194194
}
195195

196+
// NewPVCResizedEvent creates an event indicating that a PVC has been resized
197+
func NewPVCResizedEvent(apiObject APIObject, pvcname string) *Event {
198+
event := newDeploymentEvent(apiObject)
199+
event.Type = v1.EventTypeNormal
200+
event.Reason = "PVC Resized"
201+
event.Message = fmt.Sprintf("The persistent volume claim %s has been resized", pvcname)
202+
return event
203+
}
204+
205+
// NewCannotShrinkVolumeEvent creates an event indicating that the user tried to shrink a PVC
206+
func NewCannotShrinkVolumeEvent(apiObject APIObject, pvcname string) *Event {
207+
event := newDeploymentEvent(apiObject)
208+
event.Type = v1.EventTypeNormal
209+
event.Reason = "PVC Shrinked"
210+
event.Message = fmt.Sprintf("The persistent volume claim %s can not be shrinked", pvcname)
211+
return event
212+
}
213+
196214
// NewUpgradeNotAllowedEvent creates an event indicating that an upgrade (or downgrade) is not allowed.
197215
func NewUpgradeNotAllowedEvent(apiObject APIObject,
198216
fromVersion, toVersion driver.Version,

pkg/util/k8sutil/pvc.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,21 @@ type PersistentVolumeClaimInterface interface {
3737
Get(name string, options metav1.GetOptions) (*v1.PersistentVolumeClaim, error)
3838
}
3939

40-
// IsPersistentVolumeClaimMarkedForDeletion returns true if the pod has been marked for deletion.
40+
// IsPersistentVolumeClaimMarkedForDeletion returns true if the pvc has been marked for deletion.
4141
func IsPersistentVolumeClaimMarkedForDeletion(pvc *v1.PersistentVolumeClaim) bool {
4242
return pvc.DeletionTimestamp != nil
4343
}
4444

45+
// IsPersistentVolumeClaimFileSystemResizePending returns true if the pvc has FileSystemResizePending set to true
46+
func IsPersistentVolumeClaimFileSystemResizePending(pvc *v1.PersistentVolumeClaim) bool {
47+
for _, c := range pvc.Status.Conditions {
48+
if c.Type == v1.PersistentVolumeClaimFileSystemResizePending && c.Status == v1.ConditionTrue {
49+
return true
50+
}
51+
}
52+
return false
53+
}
54+
4555
// CreatePersistentVolumeClaimName returns the name of the persistent volume claim for a member with
4656
// a given id in a deployment with a given name.
4757
func CreatePersistentVolumeClaimName(deploymentName, role, id string) string {

tests/persistent_volumes_test.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,24 @@ import (
2525
"fmt"
2626
"strings"
2727
"testing"
28+
"time"
29+
30+
"github.com/arangodb/arangosync/pkg/retry"
2831

2932
"github.com/dchest/uniuri"
3033
"github.com/stretchr/testify/assert"
3134

3235
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha"
3336
kubeArangoClient "github.com/arangodb/kube-arangodb/pkg/client"
37+
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
38+
corev1 "k8s.io/api/core/v1"
39+
"k8s.io/apimachinery/pkg/api/resource"
40+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3441
//"github.com/arangodb/kube-arangodb/pkg/util"
3542
)
3643

3744
// TODO - add description
38-
func TestPersistence(t *testing.T) {
45+
func TestPVCExists(t *testing.T) {
3946
longOrSkip(t)
4047

4148
k8sNameSpace := getNamespace(t)
@@ -78,3 +85,89 @@ func TestPersistence(t *testing.T) {
7885
// Cleanup
7986
removeDeployment(deploymentClient, deploymentTemplate.GetName(), k8sNameSpace)
8087
}
88+
89+
func TestPVCResize(t *testing.T) {
90+
longOrSkip(t)
91+
92+
k8sNameSpace := getNamespace(t)
93+
k8sClient := mustNewKubeClient(t)
94+
95+
mode := api.DeploymentModeCluster
96+
engine := api.StorageEngineRocksDB
97+
98+
size10GB, _ := resource.ParseQuantity("10Gi")
99+
size08GB, _ := resource.ParseQuantity("8Gi")
100+
101+
deploymentClient := kubeArangoClient.MustNewInCluster()
102+
deploymentTemplate := newDeployment(strings.Replace(fmt.Sprintf("trsz-%s-%s-%s", mode[:2], engine[:2], uniuri.NewLen(4)), ".", "", -1))
103+
deploymentTemplate.Spec.Mode = api.NewMode(mode)
104+
deploymentTemplate.Spec.StorageEngine = api.NewStorageEngine(engine)
105+
deploymentTemplate.Spec.TLS = api.TLSSpec{}
106+
deploymentTemplate.Spec.DBServers.Resources.Requests = corev1.ResourceList{corev1.ResourceStorage: size08GB}
107+
deploymentTemplate.Spec.SetDefaults(deploymentTemplate.GetName()) // this must be last
108+
assert.NoError(t, deploymentTemplate.Spec.Validate())
109+
110+
// Create deployment
111+
_, err := deploymentClient.DatabaseV1alpha().ArangoDeployments(k8sNameSpace).Create(deploymentTemplate)
112+
defer removeDeployment(deploymentClient, deploymentTemplate.GetName(), k8sNameSpace)
113+
assert.NoError(t, err, "failed to create deplyment: %s", err)
114+
115+
depl, err := waitUntilDeployment(deploymentClient, deploymentTemplate.GetName(), k8sNameSpace, deploymentIsReady())
116+
assert.NoError(t, err, fmt.Sprintf("Deployment not running in time: %s", err))
117+
118+
// Get list of all pvcs for dbservers
119+
for _, m := range depl.Status.Members.DBServers {
120+
pvc, err := k8sClient.CoreV1().PersistentVolumeClaims(k8sNameSpace).Get(m.PersistentVolumeClaimName, metav1.GetOptions{})
121+
assert.NoError(t, err, "failed to get pvc: %s", err)
122+
volumeSize, ok := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
123+
assert.True(t, ok, "pvc does not have storage resource")
124+
assert.True(t, volumeSize.Cmp(size08GB) == 0, "wrong volume size: expected: %s, found: %s", size08GB.String(), volumeSize.String())
125+
}
126+
127+
// Update the deployment
128+
// Try to change image version
129+
depl, err = updateDeployment(deploymentClient, deploymentTemplate.GetName(), k8sNameSpace,
130+
func(depl *api.DeploymentSpec) {
131+
depl.DBServers.Resources.Requests[corev1.ResourceStorage] = size10GB
132+
})
133+
if err != nil {
134+
t.Fatalf("Failed to update the deployment")
135+
} else {
136+
t.Log("Updated deployment")
137+
}
138+
139+
if err := retry.Retry(func() error {
140+
// Get list of all pvcs for dbservers and check for new size
141+
for _, m := range depl.Status.Members.DBServers {
142+
pvc, err := k8sClient.CoreV1().PersistentVolumeClaims(k8sNameSpace).Get(m.PersistentVolumeClaimName, metav1.GetOptions{})
143+
if err != nil {
144+
return err
145+
}
146+
volumeSize, ok := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
147+
if !ok {
148+
return fmt.Errorf("pvc does not have storage resource")
149+
}
150+
if volumeSize.Cmp(size10GB) != 0 {
151+
return fmt.Errorf("wrong pvc size: expected: %s, found: %s", size10GB.String(), volumeSize.String())
152+
}
153+
volume, err := k8sClient.CoreV1().PersistentVolumes().Get(pvc.Spec.VolumeName, metav1.GetOptions{})
154+
if err != nil {
155+
return err
156+
}
157+
volumeSize, ok = volume.Spec.Capacity[corev1.ResourceStorage]
158+
if !ok {
159+
return fmt.Errorf("pv does not have storage resource")
160+
}
161+
if volumeSize.Cmp(size10GB) != 0 {
162+
return fmt.Errorf("wrong volume size: expected: %s, found: %s", size10GB.String(), volumeSize.String())
163+
}
164+
if k8sutil.IsPersistentVolumeClaimFileSystemResizePending(pvc) {
165+
return fmt.Errorf("persistent volume claim file system resize pending")
166+
}
167+
}
168+
return nil
169+
}, 5*time.Minute); err != nil {
170+
t.Fatalf("PVCs not resized: %s", err.Error())
171+
}
172+
173+
}

0 commit comments

Comments
 (0)