mirror of https://github.com/kubernetes/kops.git
916 lines
24 KiB
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)
|
|
}
|
|
}
|