2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-07-01 04:40:54 -04:00
|
|
|
package controllers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2022-07-01 10:44:38 -04:00
|
|
|
"sync"
|
2022-07-01 04:40:54 -04:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
2023-01-06 06:08:25 -05:00
|
|
|
"k8s.io/apimachinery/pkg/version"
|
2022-07-01 04:40:54 -04:00
|
|
|
|
2023-01-04 17:21:05 -05:00
|
|
|
mainconstants "github.com/edgelesssys/constellation/v2/internal/constants"
|
2023-01-04 13:04:28 -05:00
|
|
|
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
|
2022-07-01 04:40:54 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestAnnotateNodes(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
getScalingGroupErr error
|
|
|
|
getNodeImageErr error
|
|
|
|
patchErr error
|
|
|
|
node corev1.Node
|
|
|
|
nodeAfterPatch corev1.Node
|
|
|
|
wantAnnotated *corev1.Node
|
|
|
|
}{
|
|
|
|
"node is not annotated": {
|
|
|
|
node: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node-name",
|
|
|
|
},
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
nodeAfterPatch: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
nodeImageAnnotation: "node-image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
wantAnnotated: &corev1.Node{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
Kind: "Node",
|
|
|
|
APIVersion: "v1",
|
|
|
|
},
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
nodeImageAnnotation: "node-image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"node is already annotated": {
|
|
|
|
node: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
nodeImageAnnotation: "node-image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
wantAnnotated: &corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
nodeImageAnnotation: "node-image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"node is missing providerID": {},
|
|
|
|
"unable to retrieve scaling group": {
|
|
|
|
getScalingGroupErr: errors.New("error"),
|
|
|
|
node: corev1.Node{
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"unable to retrieve node image": {
|
|
|
|
getNodeImageErr: errors.New("error"),
|
|
|
|
node: corev1.Node{
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"unable to patch node annotations": {
|
|
|
|
patchErr: errors.New("error"),
|
|
|
|
node: corev1.Node{
|
|
|
|
Spec: corev1.NodeSpec{ProviderID: "provider-id"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
2023-01-03 06:09:53 -05:00
|
|
|
reconciler := NodeVersionReconciler{
|
2022-07-01 04:40:54 -04:00
|
|
|
nodeReplacer: &stubNodeReplacerReader{
|
|
|
|
nodeImage: "node-image",
|
|
|
|
scalingGroupID: "scaling-group-id",
|
|
|
|
scalingGroupIDErr: tc.getScalingGroupErr,
|
|
|
|
nodeImageErr: tc.getNodeImageErr,
|
|
|
|
},
|
|
|
|
Client: &stubReadWriterClient{
|
|
|
|
stubReaderClient: *newStubReaderClient(t, []runtime.Object{&tc.nodeAfterPatch}, nil, nil),
|
|
|
|
stubWriterClient: stubWriterClient{
|
|
|
|
patchErr: tc.patchErr,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
annotated, invalid := reconciler.annotateNodes(context.Background(), []corev1.Node{tc.node})
|
|
|
|
if tc.wantAnnotated == nil {
|
|
|
|
assert.Len(annotated, 0)
|
|
|
|
assert.Len(invalid, 1)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Len(annotated, 1)
|
|
|
|
assert.Len(invalid, 0)
|
|
|
|
assert.Equal(*tc.wantAnnotated, annotated[0])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPairDonorsAndHeirs(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
outdatedNode corev1.Node
|
|
|
|
mintNode mintNode
|
|
|
|
wantPair *replacementPair
|
|
|
|
}{
|
|
|
|
"nodes have same scaling group": {
|
|
|
|
outdatedNode: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "outdated-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mintNode: mintNode{
|
|
|
|
pendingNode: updatev1alpha1.PendingNode{
|
|
|
|
Spec: updatev1alpha1.PendingNodeSpec{
|
|
|
|
ScalingGroupID: "scaling-group-id",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
node: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "mint-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantPair: &replacementPair{
|
|
|
|
donor: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "outdated-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
heirAnnotation: "mint-name",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
heir: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "mint-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
donorAnnotation: "outdated-name",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"nodes have different scaling groups": {
|
|
|
|
outdatedNode: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "outdated-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
mintNode: mintNode{
|
|
|
|
pendingNode: updatev1alpha1.PendingNode{
|
|
|
|
Spec: updatev1alpha1.PendingNodeSpec{
|
|
|
|
ScalingGroupID: "scaling-group-2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
node: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "mint-name",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
2023-01-03 06:09:53 -05:00
|
|
|
reconciler := NodeVersionReconciler{
|
2022-07-01 04:40:54 -04:00
|
|
|
nodeReplacer: &stubNodeReplacerReader{},
|
|
|
|
Client: &stubReadWriterClient{
|
|
|
|
stubReaderClient: *newStubReaderClient(t, []runtime.Object{&tc.outdatedNode, &tc.mintNode.node}, nil, nil),
|
|
|
|
},
|
|
|
|
}
|
2023-01-03 06:09:53 -05:00
|
|
|
nodeImage := updatev1alpha1.NodeVersion{}
|
2022-07-01 04:40:54 -04:00
|
|
|
pairs := reconciler.pairDonorsAndHeirs(context.Background(), &nodeImage, []corev1.Node{tc.outdatedNode}, []mintNode{tc.mintNode})
|
|
|
|
if tc.wantPair == nil {
|
|
|
|
assert.Len(pairs, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Len(pairs, 1)
|
|
|
|
assert.Equal(*tc.wantPair, pairs[0])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMatchDonorsAndHeirs(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
donor, heir corev1.Node
|
|
|
|
wantPair *replacementPair
|
|
|
|
}{
|
|
|
|
"nodes match": {
|
|
|
|
donor: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "donor",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
heirAnnotation: "heir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
heir: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "heir",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
donorAnnotation: "donor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantPair: &replacementPair{
|
|
|
|
donor: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "donor",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
heirAnnotation: "heir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
heir: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "heir",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
donorAnnotation: "donor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"nodes do not match": {
|
|
|
|
donor: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "donor",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
heirAnnotation: "other-heir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
heir: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "heir",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group-id",
|
|
|
|
donorAnnotation: "other-donor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
|
2023-01-03 06:09:53 -05:00
|
|
|
reconciler := NodeVersionReconciler{
|
2022-07-01 04:40:54 -04:00
|
|
|
nodeReplacer: &stubNodeReplacerReader{},
|
|
|
|
Client: &stubReadWriterClient{
|
|
|
|
stubReaderClient: *newStubReaderClient(t, []runtime.Object{&tc.donor, &tc.heir}, nil, nil),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
pairs := reconciler.matchDonorsAndHeirs(context.Background(), nil, []corev1.Node{tc.donor}, []corev1.Node{tc.heir})
|
|
|
|
if tc.wantPair == nil {
|
|
|
|
assert.Len(pairs, 0)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Len(pairs, 1)
|
|
|
|
assert.Equal(*tc.wantPair, pairs[0])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateNewNodes(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
outdatedNodes []corev1.Node
|
|
|
|
pendingNodes []updatev1alpha1.PendingNode
|
|
|
|
scalingGroupByID map[string]updatev1alpha1.ScalingGroup
|
|
|
|
budget int
|
|
|
|
wantCreateCalls []string
|
|
|
|
}{
|
|
|
|
"no outdated nodes": {
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
},
|
|
|
|
"single outdated node": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
wantCreateCalls: []string{"scaling-group"},
|
|
|
|
},
|
|
|
|
"budget larger than needed": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 2,
|
|
|
|
wantCreateCalls: []string{"scaling-group"},
|
|
|
|
},
|
|
|
|
"no budget": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"scaling group image is outdated": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "outdated-image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
},
|
|
|
|
"pending node exists": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pendingNodes: []updatev1alpha1.PendingNode{
|
|
|
|
{
|
|
|
|
Spec: updatev1alpha1.PendingNodeSpec{
|
|
|
|
Goal: updatev1alpha1.NodeGoalJoin,
|
|
|
|
ScalingGroupID: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
},
|
|
|
|
"leaving pending node is ignored": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pendingNodes: []updatev1alpha1.PendingNode{
|
|
|
|
{
|
|
|
|
Spec: updatev1alpha1.PendingNodeSpec{
|
|
|
|
Goal: updatev1alpha1.NodeGoalLeave,
|
|
|
|
ScalingGroupID: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
wantCreateCalls: []string{"scaling-group"},
|
|
|
|
},
|
|
|
|
"freshly chosen donor node is skipped": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
heirAnnotation: "heir",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
},
|
|
|
|
"scaling group exists without outdated nodes": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
scalingGroupByID: map[string]updatev1alpha1.ScalingGroup{
|
|
|
|
"scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"other-scaling-group": {
|
2022-08-05 06:17:53 -04:00
|
|
|
Spec: updatev1alpha1.ScalingGroupSpec{
|
|
|
|
GroupID: "other-scaling-group",
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Status: updatev1alpha1.ScalingGroupStatus{
|
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 2,
|
|
|
|
wantCreateCalls: []string{"scaling-group"},
|
|
|
|
},
|
|
|
|
"scaling group does not exist": {
|
|
|
|
outdatedNodes: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "node",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: "scaling-group",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
budget: 1,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
require := require.New(t)
|
|
|
|
|
2023-01-03 06:09:53 -05:00
|
|
|
desiredNodeImage := updatev1alpha1.NodeVersion{
|
|
|
|
Spec: updatev1alpha1.NodeVersionSpec{
|
2022-07-01 04:40:54 -04:00
|
|
|
ImageReference: "image",
|
|
|
|
},
|
|
|
|
}
|
2023-01-03 06:09:53 -05:00
|
|
|
reconciler := NodeVersionReconciler{
|
2022-07-01 04:40:54 -04:00
|
|
|
nodeReplacer: &stubNodeReplacerWriter{},
|
|
|
|
Client: &stubReadWriterClient{
|
|
|
|
stubReaderClient: *newStubReaderClient(t, []runtime.Object{}, nil, nil),
|
|
|
|
},
|
|
|
|
Scheme: getScheme(t),
|
|
|
|
}
|
2022-11-21 12:01:23 -05:00
|
|
|
newNodeConfig := newNodeConfig{desiredNodeImage, tc.outdatedNodes, tc.pendingNodes, tc.scalingGroupByID, tc.budget}
|
|
|
|
err := reconciler.createNewNodes(context.Background(), newNodeConfig)
|
2022-07-01 04:40:54 -04:00
|
|
|
require.NoError(err)
|
|
|
|
assert.Equal(tc.wantCreateCalls, reconciler.nodeReplacer.(*stubNodeReplacerWriter).createCalls)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGroupNodes(t *testing.T) {
|
|
|
|
latestImageReference := "latest-image"
|
2023-01-03 06:09:53 -05:00
|
|
|
latestK8sComponentsReference := "latest-k8s-components-ref"
|
2022-07-01 04:40:54 -04:00
|
|
|
scalingGroup := "scaling-group"
|
|
|
|
wantNodeGroups := nodeGroups{
|
|
|
|
Outdated: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "outdated",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
|
|
|
nodeImageAnnotation: "old-image",
|
2023-01-03 06:09:53 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "outdated",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: "old-ref",
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
UpToDate: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "uptodate",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Donors: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "donor",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: "old-image",
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
|
|
|
heirAnnotation: "heir",
|
2023-01-03 06:09:53 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "donor",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: "old-ref",
|
|
|
|
heirAnnotation: "heir",
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Heirs: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "heir",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
|
|
|
donorAnnotation: "donor",
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-01-06 06:08:25 -05:00
|
|
|
AwaitingAnnotation: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "awaiting-annotation-missing-components-ref",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
donorAnnotation: "donor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "awaiting-annotation-missing-image-ref",
|
|
|
|
Annotations: map[string]string{
|
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
|
|
|
donorAnnotation: "donor",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-07-01 04:40:54 -04:00
|
|
|
Obsolete: []corev1.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "obsolete",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
|
|
|
obsoleteAnnotation: "true",
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Mint: []mintNode{
|
|
|
|
{
|
|
|
|
node: corev1.Node{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "mint",
|
|
|
|
Annotations: map[string]string{
|
2023-01-06 14:48:03 -05:00
|
|
|
scalingGroupAnnotation: scalingGroup,
|
|
|
|
nodeImageAnnotation: latestImageReference,
|
|
|
|
mainconstants.NodeKubernetesComponentsAnnotationKey: latestK8sComponentsReference,
|
2022-07-01 04:40:54 -04:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pendingNode: updatev1alpha1.PendingNode{
|
|
|
|
Spec: updatev1alpha1.PendingNodeSpec{
|
|
|
|
NodeName: "mint",
|
|
|
|
Goal: updatev1alpha1.NodeGoalJoin,
|
|
|
|
},
|
|
|
|
Status: updatev1alpha1.PendingNodeStatus{
|
|
|
|
CSPNodeState: updatev1alpha1.NodeStateReady,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
nodes := []corev1.Node{}
|
|
|
|
nodes = append(nodes, wantNodeGroups.Outdated...)
|
|
|
|
nodes = append(nodes, wantNodeGroups.UpToDate...)
|
|
|
|
nodes = append(nodes, wantNodeGroups.Donors...)
|
|
|
|
nodes = append(nodes, wantNodeGroups.Heirs...)
|
2023-01-06 06:08:25 -05:00
|
|
|
nodes = append(nodes, wantNodeGroups.AwaitingAnnotation...)
|
2022-07-01 04:40:54 -04:00
|
|
|
nodes = append(nodes, wantNodeGroups.Obsolete...)
|
|
|
|
nodes = append(nodes, wantNodeGroups.Mint[0].node)
|
|
|
|
pendingNodes := []updatev1alpha1.PendingNode{
|
|
|
|
wantNodeGroups.Mint[0].pendingNode,
|
|
|
|
}
|
|
|
|
|
|
|
|
assert := assert.New(t)
|
2023-01-03 06:09:53 -05:00
|
|
|
groups := groupNodes(nodes, pendingNodes, latestImageReference, latestK8sComponentsReference)
|
2022-07-01 04:40:54 -04:00
|
|
|
assert.Equal(wantNodeGroups, groups)
|
|
|
|
}
|
|
|
|
|
2022-07-01 10:44:38 -04:00
|
|
|
type stubNodeReplacer struct {
|
|
|
|
sync.RWMutex
|
|
|
|
nodeImages map[string]string
|
|
|
|
scalingGroups map[string]string
|
|
|
|
createNodeName string
|
|
|
|
createProviderID string
|
|
|
|
nodeImageErr error
|
|
|
|
scalingGroupIDErr error
|
|
|
|
createErr error
|
|
|
|
deleteErr error
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacer) GetNodeImage(_ context.Context, providerID string) (string, error) {
|
2022-07-01 10:44:38 -04:00
|
|
|
r.RLock()
|
|
|
|
defer r.RUnlock()
|
|
|
|
return r.nodeImages[providerID], r.nodeImageErr
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacer) GetScalingGroupID(_ context.Context, providerID string) (string, error) {
|
2022-07-01 10:44:38 -04:00
|
|
|
r.RLock()
|
|
|
|
defer r.RUnlock()
|
|
|
|
return r.scalingGroups[providerID], r.scalingGroupIDErr
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacer) CreateNode(_ context.Context, _ string) (nodeName, providerID string, err error) {
|
2022-07-01 10:44:38 -04:00
|
|
|
r.RLock()
|
|
|
|
defer r.RUnlock()
|
|
|
|
return r.createNodeName, r.createProviderID, r.createErr
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacer) DeleteNode(_ context.Context, _ string) error {
|
2022-07-01 10:44:38 -04:00
|
|
|
r.RLock()
|
|
|
|
defer r.RUnlock()
|
|
|
|
return r.deleteErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// thread safe methods to update the stub while in use
|
|
|
|
|
|
|
|
func (r *stubNodeReplacer) setNodeImage(providerID, image string) {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
if r.nodeImages == nil {
|
|
|
|
r.nodeImages = make(map[string]string)
|
|
|
|
}
|
|
|
|
r.nodeImages[providerID] = image
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *stubNodeReplacer) setScalingGroupID(providerID, scalingGroupID string) {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
if r.scalingGroups == nil {
|
|
|
|
r.scalingGroups = make(map[string]string)
|
|
|
|
}
|
|
|
|
r.scalingGroups[providerID] = scalingGroupID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *stubNodeReplacer) setCreatedNode(nodeName, providerID string, err error) {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
r.createNodeName = nodeName
|
|
|
|
r.createProviderID = providerID
|
|
|
|
r.createErr = err
|
|
|
|
}
|
|
|
|
|
2023-04-18 08:20:41 -04:00
|
|
|
func (r *stubNodeReplacer) reset() {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
r.nodeImages = nil
|
|
|
|
r.scalingGroups = nil
|
|
|
|
r.createNodeName = ""
|
|
|
|
r.createProviderID = ""
|
|
|
|
r.createErr = nil
|
|
|
|
r.deleteErr = nil
|
|
|
|
}
|
|
|
|
|
2023-01-06 06:08:25 -05:00
|
|
|
type stubKubernetesServerVersionGetter struct {
|
|
|
|
version string
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *stubKubernetesServerVersionGetter) ServerVersion() (*version.Info, error) {
|
|
|
|
return &version.Info{GitVersion: g.version}, g.err
|
|
|
|
}
|
|
|
|
|
2022-07-01 04:40:54 -04:00
|
|
|
type stubNodeReplacerReader struct {
|
|
|
|
nodeImage string
|
|
|
|
scalingGroupID string
|
|
|
|
nodeImageErr error
|
|
|
|
scalingGroupIDErr error
|
|
|
|
unimplementedNodeReplacer
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacerReader) GetNodeImage(_ context.Context, _ string) (string, error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
return r.nodeImage, r.nodeImageErr
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacerReader) GetScalingGroupID(_ context.Context, _ string) (string, error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
return r.scalingGroupID, r.scalingGroupIDErr
|
|
|
|
}
|
|
|
|
|
|
|
|
type stubNodeReplacerWriter struct {
|
|
|
|
createNodeName string
|
|
|
|
createProviderID string
|
|
|
|
createErr error
|
|
|
|
deleteErr error
|
|
|
|
|
|
|
|
createCalls []string
|
|
|
|
deleteCalls []string
|
|
|
|
|
|
|
|
unimplementedNodeReplacer
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacerWriter) CreateNode(_ context.Context, scalingGroupID string) (nodeName, providerID string, err error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
r.createCalls = append(r.createCalls, scalingGroupID)
|
|
|
|
return r.createNodeName, r.createProviderID, r.createErr
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (r *stubNodeReplacerWriter) DeleteNode(_ context.Context, providerID string) error {
|
2022-07-01 04:40:54 -04:00
|
|
|
r.deleteCalls = append(r.deleteCalls, providerID)
|
|
|
|
return r.deleteErr
|
|
|
|
}
|
|
|
|
|
|
|
|
type unimplementedNodeReplacer struct{}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (*unimplementedNodeReplacer) GetNodeImage(_ context.Context, _ string) (string, error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (*unimplementedNodeReplacer) GetScalingGroupID(_ context.Context, _ string) (string, error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (*unimplementedNodeReplacer) CreateNode(_ context.Context, _ string) (nodeName, providerID string, err error) {
|
2022-07-01 04:40:54 -04:00
|
|
|
panic("unimplemented")
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:03:36 -04:00
|
|
|
func (*unimplementedNodeReplacer) DeleteNode(_ context.Context, _ string) error {
|
2022-07-01 04:40:54 -04:00
|
|
|
panic("unimplemented")
|
|
|
|
}
|