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:
parent
be26f9ad56
commit
f96273b64a
4 changed files with 303 additions and 2 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
263
pkg/deployment/reconcile/plan_builder_volume_test.go
Normal file
263
pkg/deployment/reconcile/plan_builder_volume_test.go
Normal 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{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue