1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-15 17:51:03 +00:00

[Feature] Check if Volume with LocalStorage is missing (#1315)

This commit is contained in:
Adam Janikowski 2023-05-24 21:54:20 +02:00 committed by GitHub
parent be26f9ad56
commit f96273b64a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 303 additions and 2 deletions

View file

@ -9,6 +9,7 @@
- (Feature) Features startup logging - (Feature) Features startup logging
- (Maintenance) Generics for type handling - (Maintenance) Generics for type handling
- (Bugfix) Fix creating sync components with EA type set to Managed and headless svc - (Bugfix) Fix creating sync components with EA type set to Managed and headless svc
- (Feature) Check if Volume with LocalStorage is missing
## [1.2.27](https://github.com/arangodb/kube-arangodb/tree/1.2.27) (2023-04-27) ## [1.2.27](https://github.com/arangodb/kube-arangodb/tree/1.2.27) (2023-04-27)
- (Feature) Add InSync Cache - (Feature) Add InSync Cache

View file

@ -26,6 +26,7 @@ import (
core "k8s.io/api/core/v1" core "k8s.io/api/core/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
sharedApis "github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared" "github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector" inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
@ -51,7 +52,8 @@ func (r *Reconciler) updateMemberConditionTypeMemberVolumeUnschedulableCondition
if volumeName := pvc.Spec.VolumeName; volumeName != "" { if volumeName := pvc.Spec.VolumeName; volumeName != "" {
if pv, ok := volumeClient.GetSimple(volumeName); ok { if pv, ok := volumeClient.GetSimple(volumeName); ok {
// We have volume and volumeclaim, lets calculate condition // We have volume and volumeclaim, lets calculate condition
unschedulable := memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc) unschedulable := memberConditionTypeMemberVolumeUnschedulableCalculate(cache, pv, pvc,
memberConditionTypeMemberVolumeUnschedulableLocalStorageGone)
if unschedulable == e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) { if unschedulable == e.Member.Conditions.IsTrue(api.ConditionTypeMemberVolumeUnschedulable) {
continue continue
@ -82,3 +84,37 @@ func memberConditionTypeMemberVolumeUnschedulableCalculate(cache inspectorInterf
return false return false
} }
func memberConditionTypeMemberVolumeUnschedulableLocalStorageGone(cache inspectorInterface.Inspector, pv *core.PersistentVolume, _ *core.PersistentVolumeClaim) bool {
nodes, err := cache.Node().V1()
if err != nil {
return false
}
if pv.Spec.PersistentVolumeSource.Local == nil {
// We are not on LocalStorage
return false
}
if nodeAffinity := pv.Spec.NodeAffinity; nodeAffinity != nil {
if required := nodeAffinity.Required; required != nil {
for _, nst := range required.NodeSelectorTerms {
for _, expr := range nst.MatchExpressions {
if expr.Key == sharedApis.TopologyKeyHostname && expr.Operator == core.NodeSelectorOpIn {
// We got exact key which is required for PV
if len(expr.Values) == 1 {
// Only one host assigned, we use it as localStorage - check if node exists
_, ok := nodes.GetSimple(expr.Values[0])
if !ok {
// Node is missing!
return true
}
}
}
}
}
}
}
return false
}

View file

@ -0,0 +1,263 @@
//
// DISCLAIMER
//
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package reconcile
import (
"context"
"testing"
"github.com/stretchr/testify/require"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
"github.com/arangodb/kube-arangodb/pkg/util/tests"
)
func Test_MemberConditionTypeMemberVolumeUnschedulableLocalStorageGone(t *testing.T) {
type testCase struct {
pv core.PersistentVolumeSpec
node *core.Node
result bool
}
testCases := map[string]testCase{
"Non LocalVolume": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
GCEPersistentDisk: &core.GCEPersistentDiskVolumeSource{},
},
NodeAffinity: nil,
},
},
"LocalVolume without selectors": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: nil,
},
},
"LocalVolume with partial selectors - NPE#1": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{},
},
},
"LocalVolume with partial selectors - NPE#2": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{},
},
},
},
"LocalVolume with partial selectors - NPE#3": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{},
},
},
},
},
"LocalVolume with partial selectors - NPE#4": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{},
},
},
},
},
},
},
"LocalVolume with invalid selector key": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{
{
Key: shared.NodeArchAffinityLabel,
Operator: core.NodeSelectorOpIn,
Values: []string{
"node",
},
},
},
},
},
},
},
},
},
"LocalVolume with invalid selector operator": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{
{
Key: shared.TopologyKeyHostname,
Operator: core.NodeSelectorOpDoesNotExist,
Values: []string{
"node",
},
},
},
},
},
},
},
},
},
"LocalVolume with valid selector - existing node": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{
{
Key: shared.TopologyKeyHostname,
Operator: core.NodeSelectorOpIn,
Values: []string{
"node",
},
},
},
},
},
},
},
},
node: &core.Node{
ObjectMeta: meta.ObjectMeta{
Name: "node",
},
},
},
"LocalVolume with valid selector - missing node #1": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{
{
Key: shared.TopologyKeyHostname,
Operator: core.NodeSelectorOpIn,
Values: []string{
"node",
},
},
},
},
},
},
},
},
node: &core.Node{
ObjectMeta: meta.ObjectMeta{
Name: "node1",
},
},
result: true,
},
"LocalVolume with valid selector - missing node #2": {
pv: core.PersistentVolumeSpec{
PersistentVolumeSource: core.PersistentVolumeSource{
Local: &core.LocalVolumeSource{},
},
NodeAffinity: &core.VolumeNodeAffinity{
Required: &core.NodeSelector{
NodeSelectorTerms: []core.NodeSelectorTerm{
{
MatchExpressions: []core.NodeSelectorRequirement{
{
Key: shared.TopologyKeyHostname,
Operator: core.NodeSelectorOpIn,
Values: []string{
"node",
},
},
},
},
},
},
},
},
result: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
client := kclient.NewFakeClient()
if tc.node != nil {
_, err := client.Kubernetes().CoreV1().Nodes().Create(context.Background(), tc.node, meta.CreateOptions{})
require.NoError(t, err)
}
ins := tests.NewInspector(t, client)
require.Equal(t, tc.result, memberConditionTypeMemberVolumeUnschedulableLocalStorageGone(ins, &core.PersistentVolume{
Spec: tc.pv,
}, &core.PersistentVolumeClaim{}))
})
}
}

View file

@ -37,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
api "github.com/arangodb/kube-arangodb/pkg/apis/storage/v1alpha" api "github.com/arangodb/kube-arangodb/pkg/apis/storage/v1alpha"
"github.com/arangodb/kube-arangodb/pkg/storage/provisioner" "github.com/arangodb/kube-arangodb/pkg/storage/provisioner"
resources "github.com/arangodb/kube-arangodb/pkg/storage/resources" resources "github.com/arangodb/kube-arangodb/pkg/storage/resources"
@ -271,7 +272,7 @@ func createNodeSelector(nodeName string) *core.NodeSelector {
core.NodeSelectorTerm{ core.NodeSelectorTerm{
MatchExpressions: []core.NodeSelectorRequirement{ MatchExpressions: []core.NodeSelectorRequirement{
core.NodeSelectorRequirement{ core.NodeSelectorRequirement{
Key: "kubernetes.io/hostname", Key: shared.TopologyKeyHostname,
Operator: core.NodeSelectorOpIn, Operator: core.NodeSelectorOpIn,
Values: []string{nodeName}, Values: []string{nodeName},
}, },