Skip to content

Commit f96273b

Browse files
authored
[Feature] Check if Volume with LocalStorage is missing (#1315)
1 parent be26f9a commit f96273b

File tree

4 files changed

+303
-2
lines changed

4 files changed

+303
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- (Feature) Features startup logging
1010
- (Maintenance) Generics for type handling
1111
- (Bugfix) Fix creating sync components with EA type set to Managed and headless svc
12+
- (Feature) Check if Volume with LocalStorage is missing
1213

1314
## [1.2.27](https://github.com/arangodb/kube-arangodb/tree/1.2.27) (2023-04-27)
1415
- (Feature) Add InSync Cache

pkg/deployment/reconcile/plan_builder_volume.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
core "k8s.io/api/core/v1"
2727

2828
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
29+
sharedApis "github.com/arangodb/kube-arangodb/pkg/apis/shared"
2930
"github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared"
3031
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
3132
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
@@ -51,7 +52,8 @@ func (r *Reconciler) updateMemberConditionTypeMemberVolumeUnschedulableCondition
5152
if volumeName := pvc.Spec.VolumeName; volumeName != "" {
5253
if pv, ok := volumeClient.GetSimple(volumeName); ok {
5354
// We have volume and volumeclaim, lets calculate condition
54-
unschedulable := memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc)
55+
unschedulable := memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc,
56+
memberConditionTypeMemberVolumeUnschedulableLocalStorageGone)
5557

5658
if unschedulable == e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
5759
continue
@@ -82,3 +84,37 @@ func memberConditionTypeMemberVolumeUnschedulableCalculate(cache inspectorInterf
8284

8385
return false
8486
}
87+
88+
func memberConditionTypeMemberVolumeUnschedulableLocalStorageGone(cache inspectorInterface.Inspector, pv *core.PersistentVolume, _ *core.PersistentVolumeClaim) bool {
89+
nodes, err := cache.Node().V1()
90+
if err != nil {
91+
return false
92+
}
93+
94+
if pv.Spec.PersistentVolumeSource.Local == nil {
95+
// We are not on LocalStorage
96+
return false
97+
}
98+
99+
if nodeAffinity := pv.Spec.NodeAffinity; nodeAffinity != nil {
100+
if required := nodeAffinity.Required; required != nil {
101+
for _, nst := range required.NodeSelectorTerms {
102+
for _, expr := range nst.MatchExpressions {
103+
if expr.Key == sharedApis.TopologyKeyHostname && expr.Operator == core.NodeSelectorOpIn {
104+
// We got exact key which is required for PV
105+
if len(expr.Values) == 1 {
106+
// Only one host assigned, we use it as localStorage - check if node exists
107+
_, ok := nodes.GetSimple(expr.Values[0])
108+
if !ok {
109+
// Node is missing!
110+
return true
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
return false
120+
}
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package reconcile
22+
23+
import (
24+
"context"
25+
"testing"
26+
27+
"github.com/stretchr/testify/require"
28+
core "k8s.io/api/core/v1"
29+
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
31+
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
32+
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
33+
"github.com/arangodb/kube-arangodb/pkg/util/tests"
34+
)
35+
36+
func Test_MemberConditionTypeMemberVolumeUnschedulableLocalStorageGone(t *testing.T) {
37+
type testCase struct {
38+
pv core.PersistentVolumeSpec
39+
40+
node *core.Node
41+
42+
result bool
43+
}
44+
45+
testCases := map[string]testCase{
46+
"Non LocalVolume": {
47+
pv: core.PersistentVolumeSpec{
48+
PersistentVolumeSource: core.PersistentVolumeSource{
49+
GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{},
50+
},
51+
NodeAffinity: nil,
52+
},
53+
},
54+
"LocalVolume without selectors": {
55+
pv: core.PersistentVolumeSpec{
56+
PersistentVolumeSource: core.PersistentVolumeSource{
57+
Local: &core.LocalVolumeSource{},
58+
},
59+
NodeAffinity: nil,
60+
},
61+
},
62+
"LocalVolume with partial selectors - NPE#1": {
63+
pv: core.PersistentVolumeSpec{
64+
PersistentVolumeSource: core.PersistentVolumeSource{
65+
Local: &core.LocalVolumeSource{},
66+
},
67+
NodeAffinity: &core.VolumeNodeAffinity{},
68+
},
69+
},
70+
"LocalVolume with partial selectors - NPE#2": {
71+
pv: core.PersistentVolumeSpec{
72+
PersistentVolumeSource: core.PersistentVolumeSource{
73+
Local: &core.LocalVolumeSource{},
74+
},
75+
NodeAffinity: &core.VolumeNodeAffinity{
76+
Required: &core.NodeSelector{},
77+
},
78+
},
79+
},
80+
"LocalVolume with partial selectors - NPE#3": {
81+
pv: core.PersistentVolumeSpec{
82+
PersistentVolumeSource: core.PersistentVolumeSource{
83+
Local: &core.LocalVolumeSource{},
84+
},
85+
NodeAffinity: &core.VolumeNodeAffinity{
86+
Required: &core.NodeSelector{
87+
NodeSelectorTerms: []core.NodeSelectorTerm{},
88+
},
89+
},
90+
},
91+
},
92+
"LocalVolume with partial selectors - NPE#4": {
93+
pv: core.PersistentVolumeSpec{
94+
PersistentVolumeSource: core.PersistentVolumeSource{
95+
Local: &core.LocalVolumeSource{},
96+
},
97+
NodeAffinity: &core.VolumeNodeAffinity{
98+
Required: &core.NodeSelector{
99+
NodeSelectorTerms: []core.NodeSelectorTerm{
100+
{
101+
MatchExpressions: []core.NodeSelectorRequirement{},
102+
},
103+
},
104+
},
105+
},
106+
},
107+
},
108+
"LocalVolume with invalid selector key": {
109+
pv: core.PersistentVolumeSpec{
110+
PersistentVolumeSource: core.PersistentVolumeSource{
111+
Local: &core.LocalVolumeSource{},
112+
},
113+
NodeAffinity: &core.VolumeNodeAffinity{
114+
Required: &core.NodeSelector{
115+
NodeSelectorTerms: []core.NodeSelectorTerm{
116+
{
117+
MatchExpressions: []core.NodeSelectorRequirement{
118+
{
119+
Key: shared.NodeArchAffinityLabel,
120+
Operator: core.NodeSelectorOpIn,
121+
Values: []string{
122+
"node",
123+
},
124+
},
125+
},
126+
},
127+
},
128+
},
129+
},
130+
},
131+
},
132+
"LocalVolume with invalid selector operator": {
133+
pv: core.PersistentVolumeSpec{
134+
PersistentVolumeSource: core.PersistentVolumeSource{
135+
Local: &core.LocalVolumeSource{},
136+
},
137+
NodeAffinity: &core.VolumeNodeAffinity{
138+
Required: &core.NodeSelector{
139+
NodeSelectorTerms: []core.NodeSelectorTerm{
140+
{
141+
MatchExpressions: []core.NodeSelectorRequirement{
142+
{
143+
Key: shared.TopologyKeyHostname,
144+
Operator: core.NodeSelectorOpDoesNotExist,
145+
Values: []string{
146+
"node",
147+
},
148+
},
149+
},
150+
},
151+
},
152+
},
153+
},
154+
},
155+
},
156+
"LocalVolume with valid selector - existing node": {
157+
pv: core.PersistentVolumeSpec{
158+
PersistentVolumeSource: core.PersistentVolumeSource{
159+
Local: &core.LocalVolumeSource{},
160+
},
161+
NodeAffinity: &core.VolumeNodeAffinity{
162+
Required: &core.NodeSelector{
163+
NodeSelectorTerms: []core.NodeSelectorTerm{
164+
{
165+
MatchExpressions: []core.NodeSelectorRequirement{
166+
{
167+
Key: shared.TopologyKeyHostname,
168+
Operator: core.NodeSelectorOpIn,
169+
Values: []string{
170+
"node",
171+
},
172+
},
173+
},
174+
},
175+
},
176+
},
177+
},
178+
},
179+
180+
node: &core.Node{
181+
ObjectMeta: meta.ObjectMeta{
182+
Name: "node",
183+
},
184+
},
185+
},
186+
"LocalVolume with valid selector - missing node #1": {
187+
pv: core.PersistentVolumeSpec{
188+
PersistentVolumeSource: core.PersistentVolumeSource{
189+
Local: &core.LocalVolumeSource{},
190+
},
191+
NodeAffinity: &core.VolumeNodeAffinity{
192+
Required: &core.NodeSelector{
193+
NodeSelectorTerms: []core.NodeSelectorTerm{
194+
{
195+
MatchExpressions: []core.NodeSelectorRequirement{
196+
{
197+
Key: shared.TopologyKeyHostname,
198+
Operator: core.NodeSelectorOpIn,
199+
Values: []string{
200+
"node",
201+
},
202+
},
203+
},
204+
},
205+
},
206+
},
207+
},
208+
},
209+
210+
node: &core.Node{
211+
ObjectMeta: meta.ObjectMeta{
212+
Name: "node1",
213+
},
214+
},
215+
216+
result: true,
217+
},
218+
"LocalVolume with valid selector - missing node #2": {
219+
pv: core.PersistentVolumeSpec{
220+
PersistentVolumeSource: core.PersistentVolumeSource{
221+
Local: &core.LocalVolumeSource{},
222+
},
223+
NodeAffinity: &core.VolumeNodeAffinity{
224+
Required: &core.NodeSelector{
225+
NodeSelectorTerms: []core.NodeSelectorTerm{
226+
{
227+
MatchExpressions: []core.NodeSelectorRequirement{
228+
{
229+
Key: shared.TopologyKeyHostname,
230+
Operator: core.NodeSelectorOpIn,
231+
Values: []string{
232+
"node",
233+
},
234+
},
235+
},
236+
},
237+
},
238+
},
239+
},
240+
},
241+
242+
result: true,
243+
},
244+
}
245+
246+
for name, tc := range testCases {
247+
t.Run(name, func(t *testing.T) {
248+
client := kclient.NewFakeClient()
249+
250+
if tc.node != nil {
251+
_, err := client.Kubernetes().CoreV1().Nodes().Create(context.Background(), tc.node, meta.CreateOptions{})
252+
require.NoError(t, err)
253+
}
254+
255+
ins := tests.NewInspector(t, client)
256+
257+
require.Equal(t, tc.result, memberConditionTypeMemberVolumeUnschedulableLocalStorageGone(ins, &core.PersistentVolume{
258+
Spec: tc.pv,
259+
}, &core.PersistentVolumeClaim{}))
260+
})
261+
}
262+
263+
}

pkg/storage/pv_creator.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"k8s.io/apimachinery/pkg/api/resource"
3838
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
3939

40+
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
4041
api "github.com/arangodb/kube-arangodb/pkg/apis/storage/v1alpha"
4142
"github.com/arangodb/kube-arangodb/pkg/storage/provisioner"
4243
resources "github.com/arangodb/kube-arangodb/pkg/storage/resources"
@@ -271,7 +272,7 @@ func createNodeSelector(nodeName string) *core.NodeSelector {
271272
core.NodeSelectorTerm{
272273
MatchExpressions: []core.NodeSelectorRequirement{
273274
core.NodeSelectorRequirement{
274-
Key: "kubernetes.io/hostname",
275+
Key: shared.TopologyKeyHostname,
275276
Operator: core.NodeSelectorOpIn,
276277
Values: []string{nodeName},
277278
},

0 commit comments

Comments
 (0)