kops/pkg/validation/validate_cluster_test.go

916 lines
24 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
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.
*/
package validation
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
type MockCloud struct {
awsup.MockAWSCloud
Groups map[string]*cloudinstances.CloudInstanceGroup
t *testing.T
expectedCluster *kopsapi.Cluster
expectedInstanceGroups []kopsapi.InstanceGroup
}
var _ fi.Cloud = (*MockCloud)(nil)
func BuildMockCloud(t *testing.T, groups map[string]*cloudinstances.CloudInstanceGroup, expectedCluster *kopsapi.Cluster, expectedInstanceGroups []kopsapi.InstanceGroup) *MockCloud {
m := MockCloud{
MockAWSCloud: *awsup.BuildMockAWSCloud("us-east-1", "abc"),
Groups: groups,
t: t,
expectedCluster: expectedCluster,
expectedInstanceGroups: expectedInstanceGroups,
}
return &m
}
func (c *MockCloud) GetCloudGroups(cluster *kopsapi.Cluster, instancegroups []*kopsapi.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
assert.Equal(c.t, c.expectedCluster, cluster, "cluster")
igs := make([]kopsapi.InstanceGroup, 0, len(instancegroups))
for _, ig := range instancegroups {
igs = append(igs, *ig)
}
assert.ElementsMatch(c.t, c.expectedInstanceGroups, igs)
// TODO assert nodes contains all the nodes in the mock kubernetes.Interface?
return c.Groups, nil
}
func testValidate(t *testing.T, groups map[string]*cloudinstances.CloudInstanceGroup, objects []runtime.Object) (*ValidationCluster, error) {
ctx := context.TODO()
cluster := &kopsapi.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "testcluster.k8s.local"},
Spec: kopsapi.ClusterSpec{
ExternalDNS: &kopsapi.ExternalDNSConfig{
Provider: kopsapi.ExternalDNSProviderDNSController,
},
},
}
if len(groups) == 0 {
groups = make(map[string]*cloudinstances.CloudInstanceGroup)
groups["master-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleControlPlane,
},
},
MinSize: 1,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "master-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
}
}
instanceGroups := make([]kopsapi.InstanceGroup, 0, len(groups))
objects = append([]runtime.Object(nil), objects...)
for _, g := range groups {
instanceGroups = append(instanceGroups, *g.InstanceGroup)
for _, member := range g.Ready {
node := member.Node
if node != nil {
objects = append(objects, node)
}
}
for _, member := range g.NeedUpdate {
node := member.Node
if node != nil {
objects = append(objects, node)
}
}
}
mockcloud := BuildMockCloud(t, groups, cluster, instanceGroups)
restConfig := &rest.Config{
Host: "https://api.testcluster.k8s.local",
}
validator, err := NewClusterValidator(cluster, mockcloud, &kopsapi.InstanceGroupList{Items: instanceGroups}, nil, nil, restConfig, fake.NewSimpleClientset(objects...))
if err != nil {
return nil, err
}
return validator.Validate(ctx)
}
func Test_ValidateCloudGroupMissing(t *testing.T) {
ctx := context.TODO()
cluster := &kopsapi.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "testcluster.k8s.local"},
Spec: kopsapi.ClusterSpec{
ExternalDNS: &kopsapi.ExternalDNSConfig{
Provider: kopsapi.ExternalDNSProviderDNSController,
},
},
}
instanceGroups := []kopsapi.InstanceGroup{
{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
}
mockcloud := BuildMockCloud(t, nil, cluster, instanceGroups)
restConfig := &rest.Config{
Host: "https://api.testcluster.k8s.local",
}
validator, err := NewClusterValidator(cluster, mockcloud, &kopsapi.InstanceGroupList{Items: instanceGroups}, nil, nil, restConfig, fake.NewSimpleClientset())
require.NoError(t, err)
v, err := validator.Validate(ctx)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "InstanceGroup",
Name: "node-1",
Message: "InstanceGroup \"node-1\" is missing from the cloud provider",
InstanceGroup: &instanceGroups[0],
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateNodesNotEnough(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
MinSize: 2,
TargetSize: 3,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1b"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "InstanceGroup",
Name: "node-1",
Message: "InstanceGroup \"node-1\" did not have enough nodes 2 vs 3",
InstanceGroup: groups["node-1"].InstanceGroup,
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateDetachedNodesDontCount(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
MinSize: 2,
TargetSize: 2,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1b"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
Status: cloudinstances.CloudInstanceStatusDetached,
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "InstanceGroup",
Name: "node-1",
Message: "InstanceGroup \"node-1\" did not have enough nodes 1 vs 2",
InstanceGroup: groups["node-1"].InstanceGroup,
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateNodeNotReady(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
MinSize: 2,
TargetSize: 2,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1b"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionFalse},
},
},
},
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "Node",
Name: "node-1b",
Message: "node \"node-1b\" of role \"node\" is not ready",
InstanceGroup: groups["node-1"].InstanceGroup,
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateMastersNotEnough(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleControlPlane,
},
},
MinSize: 2,
TargetSize: 3,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "master-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "master-1b"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "InstanceGroup",
Name: "master-1",
Message: "InstanceGroup \"master-1\" did not have enough nodes 2 vs 3",
InstanceGroup: groups["node-1"].InstanceGroup,
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateMasterNotReady(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleControlPlane,
},
},
MinSize: 2,
TargetSize: 2,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "master-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "master-1b"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionFalse},
},
},
},
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &ValidationError{
Kind: "Node",
Name: "master-1b",
Message: "node \"master-1b\" of role \"control-plane\" is not ready",
InstanceGroup: groups["node-1"].InstanceGroup,
}, v.Failures[0]) {
printDebug(t, v)
}
}
func Test_ValidateMasterStaticPods(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleControlPlane,
},
},
MinSize: 1,
TargetSize: 1,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1a",
Labels: map[string]string{"node-role.kubernetes.io/control-plane": ""},
},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "1.2.3.4",
},
},
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1b",
Labels: map[string]string{"node-role.kubernetes.io/control-plane": ""},
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
Addresses: []v1.NodeAddress{
{
Address: "5.6.7.8",
},
},
},
},
},
{
ID: "i-00003",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "master-1c",
Labels: map[string]string{"node-role.kubernetes.io/control-plane": ""},
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionFalse},
},
Addresses: []v1.NodeAddress{
{
Address: "9.10.11.12",
},
},
},
},
},
},
}
var podList []map[string]string
expectedFailures := []*ValidationError{
{
Kind: "Node",
Name: "master-1c",
Message: "node \"master-1c\" of role \"control-plane\" is not ready",
InstanceGroup: groups["node-1"].InstanceGroup,
},
}
for i, pod := range []string{
"kube-apiserver",
"kube-controller-manager",
"kube-scheduler",
} {
podList = append(podList, []map[string]string{
{
"name": fmt.Sprintf("pod-a-%d", i),
"ready": "true",
"k8s-app": pod,
"phase": string(v1.PodRunning),
"priorityClassName": "system-cluster-critical",
"hostip": "1.2.3.4",
},
{
"name": fmt.Sprintf("pod-b-%d", i),
"namespace": "other",
"ready": "true",
"k8s-app": pod,
"phase": string(v1.PodRunning),
"priorityClassName": "system-cluster-critical",
"hostip": "5.6.7.8",
},
}...)
expectedFailures = append(expectedFailures, &ValidationError{
Kind: "Node",
Name: "master-1b",
Message: "control-plane node \"master-1b\" is missing " + pod + " pod",
InstanceGroup: groups["node-1"].InstanceGroup,
})
}
v, err := testValidate(t, groups, makePodList(podList))
require.NoError(t, err)
if !assert.ElementsMatch(t, v.Failures, expectedFailures) {
printDebug(t, v)
}
}
func Test_ValidateNoPodFailures(t *testing.T) {
testpods := []map[string]string{}
for _, phase := range []v1.PodPhase{
v1.PodPending,
v1.PodRunning,
v1.PodSucceeded,
v1.PodFailed,
v1.PodUnknown,
} {
for _, priority := range []string{"", "otherPriority"} {
testpods = append(testpods, []map[string]string{
{
"name": fmt.Sprintf("ready-%s-%s", priority, string(phase)),
"namespace": "kube-system",
"priorityClassName": priority,
"ready": "true",
"phase": string(phase),
},
{
"name": fmt.Sprintf("notready-%s-%s", priority, string(phase)),
"namespace": "kube-system",
"priorityClassName": priority,
"ready": "false",
"phase": string(phase),
},
}...)
}
}
for _, namespace := range []string{"kube-system", "otherNamespace"} {
for _, priority := range []string{"node", "cluster"} {
testpods = append(testpods, []map[string]string{
{
"name": fmt.Sprintf("ready-%s-%s", priority, namespace),
"namespace": namespace,
"priorityClassName": fmt.Sprintf("system-%s-critical", priority),
"ready": "true",
"phase": string(v1.PodRunning),
},
{
"name": fmt.Sprintf("notready-%s-%s", priority, namespace),
"namespace": namespace,
"priorityClassName": fmt.Sprintf("system-%s-critical", priority),
"ready": "false",
"phase": string(v1.PodSucceeded),
},
}...)
}
}
v, err := testValidate(t, nil, makePodList(testpods))
require.NoError(t, err)
if !assert.Empty(t, v.Failures) {
printDebug(t, v)
}
}
func Test_ValidatePodFailure(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
MinSize: 1,
TargetSize: 1,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1a",
},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "1.2.3.4",
},
},
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1b",
},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "5.6.7.8",
},
},
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1c",
},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Address: "9.10.11.12",
},
},
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
}
for _, tc := range []struct {
name string
phase v1.PodPhase
expected string
}{
{
name: "pending",
phase: v1.PodPending,
expected: "pending",
},
{
name: "notready",
phase: v1.PodRunning,
expected: "not ready (container1,container2)",
},
{
name: "unknown",
phase: v1.PodUnknown,
expected: "unknown phase",
},
} {
for _, priority := range []string{"node", "cluster"} {
for _, namespace := range []string{"kube-system", "otherNamespace"} {
for _, hostIp := range []string{"1.2.3.4", "5.6.7.8", "9.10.11.12"} {
t.Run(fmt.Sprintf("%s-%s-%s", tc.name, priority, namespace), func(t *testing.T) {
v, err := testValidate(t, groups, makePodList(
[]map[string]string{
{
"name": "pod1",
"namespace": namespace,
"priorityClassName": fmt.Sprintf("system-%s-critical", priority),
"ready": "false",
"phase": string(tc.phase),
"hostip": hostIp,
},
},
))
var podInstanceGroup *kopsapi.InstanceGroup
if priority == "node" {
podInstanceGroup = groups["node-1"].InstanceGroup
}
expected := ValidationError{
Kind: "Pod",
Name: fmt.Sprintf("%s/pod1", namespace),
Message: fmt.Sprintf("system-%s-critical pod \"pod1\" is %s", priority, tc.expected),
InstanceGroup: podInstanceGroup,
}
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) ||
!assert.Equal(t, &expected, v.Failures[0]) {
printDebug(t, v)
}
})
}
}
}
}
}
func printDebug(t *testing.T, v *ValidationCluster) {
t.Logf("cluster - %d failures", len(v.Failures))
for _, fail := range v.Failures {
t.Logf(" failure: %+v", fail)
}
}
func dummyPod(podMap map[string]string) v1.Pod {
var labels map[string]string
if podMap["k8s-app"] != "" {
labels = map[string]string{"k8s-app": podMap["k8s-app"]}
}
namespace := podMap["namespace"]
if namespace == "" {
namespace = "kube-system"
}
return v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podMap["name"],
Namespace: namespace,
Labels: labels,
},
Spec: v1.PodSpec{
PriorityClassName: podMap["priorityClassName"],
},
Status: v1.PodStatus{
Phase: v1.PodPhase(podMap["phase"]),
ContainerStatuses: []v1.ContainerStatus{
{
Name: "container1",
Ready: podMap["ready"] == "true",
},
{
Name: "container2",
Ready: podMap["ready"] == "true",
},
},
HostIP: podMap["hostip"],
},
}
}
// MakePodList constructs api.PodList from a list of pod attributes
func makePodList(pods []map[string]string) []runtime.Object {
var list []runtime.Object
for _, pod := range pods {
p := dummyPod(pod)
list = append(list, &p)
}
return list
}
func Test_ValidateBastionNodes(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["ig1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "ig1",
},
},
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: nil,
},
},
}
// When an instancegroup's nodes are not ready, that is an error
t.Run("instancegroup's nodes not ready", func(t *testing.T) {
groups["ig1"].InstanceGroup.Spec.Role = kopsapi.InstanceGroupRoleNode
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Len(t, v.Failures, 1) {
printDebug(t, v)
} else if !assert.Equal(t, "machine \"i-00001\" has not yet joined cluster", v.Failures[0].Message) {
printDebug(t, v)
}
})
// Except for a bastion instancegroup - those are not expected to join as nodes
t.Run("bastion instancegroup nodes not ready", func(t *testing.T) {
groups["ig1"].InstanceGroup.Spec.Role = kopsapi.InstanceGroupRoleBastion
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Empty(t, v.Failures, "Bastion nodes are not expected to join cluster") {
printDebug(t, v)
}
})
}
func Test_ValidateDetachedNodesNotValidated(t *testing.T) {
groups := make(map[string]*cloudinstances.CloudInstanceGroup)
groups["node-1"] = &cloudinstances.CloudInstanceGroup{
InstanceGroup: &kopsapi.InstanceGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "node-1",
},
Spec: kopsapi.InstanceGroupSpec{
Role: kopsapi.InstanceGroupRoleNode,
},
},
MinSize: 2,
TargetSize: 2,
Ready: []*cloudinstances.CloudInstance{
{
ID: "i-00001",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
{
ID: "i-00002",
Node: &v1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-2a"},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{Type: "Ready", Status: v1.ConditionTrue},
},
},
},
},
},
NeedUpdate: []*cloudinstances.CloudInstance{
{
ID: "i-00003",
Status: cloudinstances.CloudInstanceStatusDetached,
},
},
}
v, err := testValidate(t, groups, nil)
require.NoError(t, err)
if !assert.Empty(t, v.Failures) {
printDebug(t, v)
}
}