Skip to content

Commit 953f930

Browse files
authored
[Bugfix] [LocalStorage] Add feature to pass ReclaimPolicy from StorageClass to PersistentVolumes (#1326)
1 parent 6d4b879 commit 953f930

18 files changed

+379
-76
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
44
- (Maintenance) Add govulncheck to pipeline, update golangci-linter
55
- (Feature) Agency Cache memory usage reduction
6+
- (Bugfix) (LocalStorage) Add feature to pass ReclaimPolicy from StorageClass to PersistentVolumes
67

78
## [1.2.28](https://github.com/arangodb/kube-arangodb/tree/1.2.28) (2023-06-05)
89
- (Feature) ArangoBackup create retries and MaxIterations limit

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ linter-fix:
280280
.PHONY: vulncheck
281281
vulncheck:
282282
@echo ">> Checking for known vulnerabilities"
283-
@$(GOPATH)/bin/govulncheck --tags $(RELEASE_MODE) ./...
283+
@-$(GOPATH)/bin/govulncheck --tags $(RELEASE_MODE) ./...
284284

285285
.PHONY: build
286286
build: docker manifests

pkg/apis/storage/v1alpha/local_storage_spec_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -29,22 +29,22 @@ import (
2929
// Test creation of local storage spec
3030
func TestLocalStorageSpecCreation(t *testing.T) {
3131

32-
class := StorageClassSpec{"SpecName", true}
32+
class := StorageClassSpec{"SpecName", true, nil}
3333
local := LocalStorageSpec{StorageClass: class, LocalPath: []string{""}}
3434
assert.Error(t, local.Validate())
3535

36-
class = StorageClassSpec{"spec-name", true}
36+
class = StorageClassSpec{"spec-name", true, nil}
3737
local = LocalStorageSpec{StorageClass: class, LocalPath: []string{""}}
3838
assert.Error(t, local.Validate(), "should fail as the empty sting is not a valid path")
3939

40-
class = StorageClassSpec{"spec-name", true}
40+
class = StorageClassSpec{"spec-name", true, nil}
4141
local = LocalStorageSpec{StorageClass: class, LocalPath: []string{}}
4242
assert.True(t, IsValidation(local.Validate()))
4343
}
4444

4545
// Test reset of local storage spec
4646
func TestLocalStorageSpecReset(t *testing.T) {
47-
class := StorageClassSpec{"spec-name", true}
47+
class := StorageClassSpec{"spec-name", true, nil}
4848
source := LocalStorageSpec{StorageClass: class, LocalPath: []string{"/a/path", "/another/path"}}
4949
target := LocalStorageSpec{}
5050
resetImmutableFieldsResult := source.ResetImmutableFields(&target)

pkg/apis/storage/v1alpha/storage_class_spec.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -21,14 +21,18 @@
2121
package v1alpha
2222

2323
import (
24+
core "k8s.io/api/core/v1"
25+
2426
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
27+
"github.com/arangodb/kube-arangodb/pkg/util"
2528
"github.com/arangodb/kube-arangodb/pkg/util/errors"
2629
)
2730

2831
// StorageClassSpec contains specification for create StorageClass.
2932
type StorageClassSpec struct {
30-
Name string `json:"name,omitempty"`
31-
IsDefault bool `json:"isDefault,omitempty"`
33+
Name string `json:"name,omitempty"`
34+
IsDefault bool `json:"isDefault,omitempty"`
35+
ReclaimPolicy *core.PersistentVolumeReclaimPolicy `json:"reclaimPolicy,omitempty"`
3236
}
3337

3438
// Validate the given spec, returning an error on validation
@@ -37,6 +41,13 @@ func (s StorageClassSpec) Validate() error {
3741
if err := shared.ValidateResourceName(s.Name); err != nil {
3842
return errors.WithStack(err)
3943
}
44+
45+
switch r := s.GetReclaimPolicy(); r {
46+
case core.PersistentVolumeReclaimRetain, core.PersistentVolumeReclaimDelete:
47+
default:
48+
return errors.Newf("Unsupported ReclaimPolicy: %s", r)
49+
}
50+
4051
return nil
4152
}
4253

@@ -47,6 +58,11 @@ func (s *StorageClassSpec) SetDefaults(localStorageName string) {
4758
}
4859
}
4960

61+
// GetReclaimPolicy returns StorageClass Reclaim Policy
62+
func (s *StorageClassSpec) GetReclaimPolicy() core.PersistentVolumeReclaimPolicy {
63+
return util.TypeOrDefault(s.ReclaimPolicy, core.PersistentVolumeReclaimRetain)
64+
}
65+
5066
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
5167
// It returns a list of fields that have been reset.
5268
// Field names are relative to `spec.`.
@@ -56,5 +72,9 @@ func (s StorageClassSpec) ResetImmutableFields(fieldPrefix string, target *Stora
5672
target.Name = s.Name
5773
result = append(result, fieldPrefix+"name")
5874
}
75+
if s.GetReclaimPolicy() != target.GetReclaimPolicy() {
76+
target.ReclaimPolicy = s.ReclaimPolicy
77+
result = append(result, fieldPrefix+"reclaimPolicy")
78+
}
5979
return result
6080
}

pkg/apis/storage/v1alpha/storage_class_spec_test.go

+31-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -25,6 +25,9 @@ import (
2525
"testing"
2626

2727
"github.com/stretchr/testify/assert"
28+
core "k8s.io/api/core/v1"
29+
30+
"github.com/arangodb/kube-arangodb/pkg/util"
2831
)
2932

3033
// test creation of storage class spec
@@ -35,7 +38,16 @@ func TestStorageClassSpecCreation(t *testing.T) {
3538
storageClassSpec = StorageClassSpec{Name: "TheSpecName", IsDefault: true}
3639
assert.Error(t, storageClassSpec.Validate(), "upper case letters are not allowed in resources")
3740

38-
storageClassSpec = StorageClassSpec{"the-spec-name", true}
41+
storageClassSpec = StorageClassSpec{Name: "the-spec-name", IsDefault: true, ReclaimPolicy: util.NewType[core.PersistentVolumeReclaimPolicy]("Random")}
42+
assert.Error(t, storageClassSpec.Validate(), "upper case letters are not allowed in resources")
43+
44+
storageClassSpec = StorageClassSpec{"the-spec-name", true, util.NewType(core.PersistentVolumeReclaimRetain)}
45+
assert.NoError(t, storageClassSpec.Validate())
46+
47+
storageClassSpec = StorageClassSpec{"the-spec-name", true, util.NewType(core.PersistentVolumeReclaimDelete)}
48+
assert.NoError(t, storageClassSpec.Validate())
49+
50+
storageClassSpec = StorageClassSpec{"the-spec-name", true, nil}
3951
assert.NoError(t, storageClassSpec.Validate())
4052

4153
storageClassSpec = StorageClassSpec{} // no proper name -> invalid
@@ -45,11 +57,22 @@ func TestStorageClassSpecCreation(t *testing.T) {
4557

4658
// test reset of storage class spec
4759
func TestStorageClassSpecResetImmutableFileds(t *testing.T) {
48-
specSource := StorageClassSpec{"source", true}
49-
specTarget := StorageClassSpec{"target", true}
60+
t.Run("Name", func(t *testing.T) {
61+
specSource := StorageClassSpec{"source", true, nil}
62+
specTarget := StorageClassSpec{"target", true, nil}
63+
64+
assert.Equal(t, "target", specTarget.Name)
65+
rv := specSource.ResetImmutableFields("fieldPrefix-", &specTarget)
66+
assert.Equal(t, "fieldPrefix-name", strings.Join(rv, ", "))
67+
assert.Equal(t, "source", specTarget.Name)
68+
})
69+
t.Run("ReclaimPolicy", func(t *testing.T) {
70+
specSource := StorageClassSpec{"source", true, util.NewType(core.PersistentVolumeReclaimRetain)}
71+
specTarget := StorageClassSpec{"source", true, util.NewType(core.PersistentVolumeReclaimDelete)}
5072

51-
assert.Equal(t, "target", specTarget.Name)
52-
rv := specSource.ResetImmutableFields("fieldPrefix-", &specTarget)
53-
assert.Equal(t, "fieldPrefix-name", strings.Join(rv, ", "))
54-
assert.Equal(t, "source", specTarget.Name)
73+
assert.Equal(t, core.PersistentVolumeReclaimDelete, *specTarget.ReclaimPolicy)
74+
rv := specSource.ResetImmutableFields("fieldPrefix-", &specTarget)
75+
assert.Equal(t, "fieldPrefix-reclaimPolicy", strings.Join(rv, ", "))
76+
assert.Equal(t, core.PersistentVolumeReclaimRetain, *specTarget.ReclaimPolicy)
77+
})
5578
}

pkg/apis/storage/v1alpha/zz_generated.deepcopy.go

+6-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/deployment/agency/state.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
)
3030

3131
func (c *cache) loadState(ctx context.Context, connection conn.Connection) (StateRoot, error) {
32-
resp, code, err := conn.NewExecutor[ReadRequest, StateRoots](connection).Execute(ctx, http.MethodPost, "/_api/agency/config", GetAgencyReadRequestFields())
32+
resp, code, err := conn.NewExecutor[ReadRequest, StateRoots](connection).Execute(ctx, http.MethodPost, "/_api/agency/read", GetAgencyReadRequestFields())
3333
if err != nil {
3434
return StateRoot{}, err
3535
}

pkg/deployment/client/client_cache.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (cc *cache) GetRaw(group api.ServerGroup, id string) (conn.Connection, erro
7979
return nil, err
8080
}
8181

82-
return cc.factory.RawConnection(endpoint)
82+
return cc.factory.RawConnection(cc.extendHost(m.GetEndpoint(endpoint)))
8383
}
8484

8585
func (cc *cache) Connection(ctx context.Context, host string) (driver.Connection, error) {

pkg/deployment/features/volumes.go

+13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package features
2222

2323
func init() {
2424
registerFeature(localVolumeReplacementCheck)
25+
registerFeature(localStorageReclaimPolicyPass)
2526
}
2627

2728
var localVolumeReplacementCheck Feature = &feature{
@@ -32,6 +33,18 @@ var localVolumeReplacementCheck Feature = &feature{
3233
enabledByDefault: false,
3334
}
3435

36+
var localStorageReclaimPolicyPass Feature = &feature{
37+
name: "local-storage.pass-reclaim-policy",
38+
description: "[LocalStorage] Pass ReclaimPolicy from StorageClass instead of using hardcoded Retain",
39+
version: "3.6.0",
40+
enterpriseRequired: false,
41+
enabledByDefault: false,
42+
}
43+
44+
func LocalStorageReclaimPolicyPass() Feature {
45+
return localStorageReclaimPolicyPass
46+
}
47+
3548
func LocalVolumeReplacementCheck() Feature {
3649
return localVolumeReplacementCheck
3750
}

pkg/deployment/reconcile/plan_builder_volume.go

+55-27
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package reconcile
2222

2323
import (
2424
"context"
25+
"time"
2526

2627
core "k8s.io/api/core/v1"
2728

@@ -35,6 +36,10 @@ import (
3536
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
3637
)
3738

39+
const (
40+
persistentVolumeClaimPostCreationDelay = time.Minute
41+
)
42+
3843
func (r *Reconciler) volumeMemberReplacement(ctx context.Context, apiObject k8sutil.APIObject,
3944
spec api.DeploymentSpec, status api.DeploymentStatus,
4045
context PlanBuilderContext) api.Plan {
@@ -68,6 +73,23 @@ func (r *Reconciler) volumeMemberReplacement(ctx context.Context, apiObject k8su
6873
client, ok := context.ACS().ClusterCache(member.Member.ClusterID)
6974
if ok {
7075
if pvc, ok := client.PersistentVolumeClaim().V1().GetSimple(n); ok {
76+
if pvc.Status.Phase == core.ClaimPending {
77+
continue
78+
}
79+
80+
// Check if pvc was not created too recently
81+
if t := pvc.GetCreationTimestamp(); !t.IsZero() {
82+
if time.Since(t.Time) < persistentVolumeClaimPostCreationDelay {
83+
// PVC was recreated recently, continue
84+
continue
85+
}
86+
}
87+
88+
if t := pvc.GetDeletionTimestamp(); t != nil {
89+
// PVC already under deletion
90+
return nil
91+
}
92+
7193
// Server is not part of plan and is not ready
7294
return api.Plan{actions.NewAction(api.ActionTypeRemoveMemberPVC, member.Group, member.Member, "PVC is unschedulable").AddParam("pvc", string(pvc.GetUID()))}
7395
}
@@ -87,45 +109,51 @@ func (r *Reconciler) updateMemberConditionTypeMemberVolumeUnschedulableCondition
87109

88110
cache := context.ACS().CurrentClusterCache()
89111

112+
for _, e := range status.Members.AsList() {
113+
unschedulable := memberConditionTypeMemberVolumeUnschedulableRoot(cache, e)
114+
115+
if unschedulable == e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
116+
continue
117+
} else if unschedulable && !e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
118+
plan = append(plan, shared.UpdateMemberConditionActionV2("PV Unschedulable", api.ConditionTypeMemberVolumeUnschedulable, e.Group, e.Member.ID, true,
119+
"PV Unschedulable", "PV Unschedulable", ""))
120+
} else if !unschedulable && e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
121+
plan = append(plan, shared.RemoveMemberConditionActionV2("PV Schedulable", api.ConditionTypeMemberVolumeUnschedulable, e.Group, e.Member.ID))
122+
}
123+
}
124+
125+
return plan
126+
}
127+
128+
type memberConditionTypeMemberVolumeUnschedulableCalculateFunc func(cache inspectorInterface.Inspector, pv *core.PersistentVolume, pvc *core.PersistentVolumeClaim) bool
129+
130+
func memberConditionTypeMemberVolumeUnschedulableRoot(cache inspectorInterface.Inspector, member api.DeploymentStatusMemberElement) bool {
90131
volumeClient, err := cache.PersistentVolume().V1()
91132
if err != nil {
92-
// We cant fetch volumes, continue
93-
return nil
133+
// We cant fetch volumes, remove condition as it cannot be evaluated
134+
return false
94135
}
95136

96-
for _, e := range status.Members.AsList() {
97-
if pvcStatus := e.Member.PersistentVolumeClaim; pvcStatus != nil {
98-
if pvc, ok := context.ACS().CurrentClusterCache().PersistentVolumeClaim().V1().GetSimple(pvcStatus.GetName()); ok {
99-
if volumeName := pvc.Spec.VolumeName; volumeName != "" {
100-
if pv, ok := volumeClient.GetSimple(volumeName); ok {
101-
// We have volume and volumeclaim, lets calculate condition
102-
unschedulable := memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc,
103-
memberConditionTypeMemberVolumeUnschedulableLocalStorageGone)
104-
105-
if e.Member.Conditions.IsTrue(api.ConditionTypeScheduled) {
106-
// We are scheduled, above checks can be ignored
107-
unschedulable = false
108-
}
137+
if member.Member.Conditions.IsTrue(api.ConditionTypeScheduled) {
138+
// Scheduled member ignore PV Unschedulable condition
139+
return false
140+
}
109141

110-
if unschedulable == e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
111-
continue
112-
} else if unschedulable && !e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
113-
plan = append(plan, shared.UpdateMemberConditionActionV2("PV Unschedulable", api.ConditionTypeMemberVolumeUnschedulable, e.Group, e.Member.ID, true,
114-
"PV Unschedulable", "PV Unschedulable", ""))
115-
} else if !unschedulable && e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
116-
plan = append(plan, shared.RemoveMemberConditionActionV2("PV Schedulable", api.ConditionTypeMemberVolumeUnschedulable, e.Group, e.Member.ID))
117-
}
118-
}
142+
if pvcStatus := member.Member.PersistentVolumeClaim; pvcStatus != nil {
143+
if pvc, ok := cache.PersistentVolumeClaim().V1().GetSimple(pvcStatus.GetName()); ok {
144+
if volumeName := pvc.Spec.VolumeName; volumeName != "" {
145+
if pv, ok := volumeClient.GetSimple(volumeName); ok {
146+
// We have volume and volumeclaim, lets calculate condition
147+
return memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc,
148+
memberConditionTypeMemberVolumeUnschedulableLocalStorageGone)
119149
}
120150
}
121151
}
122152
}
123153

124-
return plan
154+
return false
125155
}
126156

127-
type memberConditionTypeMemberVolumeUnschedulableCalculateFunc func(cache inspectorInterface.Inspector, pv *core.PersistentVolume, pvc *core.PersistentVolumeClaim) bool
128-
129157
func memberConditionTypeMemberVolumeUnschedulableCalculate(cache inspectorInterface.Inspector, pv *core.PersistentVolume, pvc *core.PersistentVolumeClaim, funcs ...memberConditionTypeMemberVolumeUnschedulableCalculateFunc) bool {
130158
for _, f := range funcs {
131159
if f(cache, pv, pvc) {

pkg/exporter/monitor.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// DISCLAIMER
33
//
4-
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
4+
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
55
//
66
// Licensed under the Apache License, Version 2.0 (the "License");
77
// you may not use this file except in compliance with the License.
@@ -161,6 +161,5 @@ func setPath(uri, uriPath string) (*url.URL, error) {
161161
return u, err
162162
}
163163
u.Path = path.Join(uriPath)
164-
u.Scheme = "https"
165164
return u, nil
166165
}

0 commit comments

Comments
 (0)