2017-07-10 21:30:47 -04:00
/ *
Copyright 2017 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 .
* /
2017-07-20 17:39:34 -04:00
// TODO: This file can potentially be moved to a common place used by both e2e and integration tests.
2017-07-10 21:30:47 -04:00
package framework
import (
2020-03-18 10:59:51 -04:00
"context"
"fmt"
2017-11-12 04:40:57 -05:00
"testing"
2020-03-18 10:59:51 -04:00
"time"
2017-11-12 04:40:57 -05:00
2020-10-12 14:04:47 -04:00
v1 "k8s.io/api/core/v1"
2017-11-12 04:40:57 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-03-18 10:59:51 -04:00
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2020-03-18 10:59:51 -04:00
nodectlr "k8s.io/kubernetes/pkg/controller/nodelifecycle"
)
const (
// poll is how often to Poll pods, nodes and claims.
poll = 2 * time . Second
// singleCallTimeout is how long to try single API calls (like 'get' or 'list'). Used to prevent
// transient failures from failing tests.
singleCallTimeout = 5 * time . Minute
2017-07-10 21:30:47 -04:00
)
2022-05-21 12:04:46 -04:00
// CreateNamespaceOrDie creates a namespace.
2022-10-31 14:52:48 -04:00
func CreateNamespaceOrDie ( c clientset . Interface , baseName string , t testing . TB ) * v1 . Namespace {
2022-05-21 12:04:46 -04:00
ns := & v1 . Namespace { ObjectMeta : metav1 . ObjectMeta { Name : baseName } }
result , err := c . CoreV1 ( ) . Namespaces ( ) . Create ( context . TODO ( ) , ns , metav1 . CreateOptions { } )
if err != nil {
t . Fatalf ( "Failed to create namespace: %v" , err )
}
return result
}
// DeleteNamespaceOrDie deletes a namespace.
2022-10-31 14:52:48 -04:00
func DeleteNamespaceOrDie ( c clientset . Interface , ns * v1 . Namespace , t testing . TB ) {
2022-05-21 12:04:46 -04:00
err := c . CoreV1 ( ) . Namespaces ( ) . Delete ( context . TODO ( ) , ns . Name , metav1 . DeleteOptions { } )
if err != nil {
t . Fatalf ( "Failed to delete namespace: %v" , err )
}
}
2022-11-04 12:13:21 -04:00
// waitListAllNodes is a wrapper around listing nodes supporting retries.
func waitListAllNodes ( c clientset . Interface ) ( * v1 . NodeList , error ) {
2020-03-18 10:59:51 -04:00
var nodes * v1 . NodeList
var err error
if wait . PollImmediate ( poll , singleCallTimeout , func ( ) ( bool , error ) {
2022-11-04 12:13:21 -04:00
nodes , err = c . CoreV1 ( ) . Nodes ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2020-03-18 10:59:51 -04:00
if err != nil {
return false , err
}
return true , nil
} ) != nil {
return nodes , err
}
return nodes , nil
}
// Filter filters nodes in NodeList in place, removing nodes that do not
// satisfy the given condition
func Filter ( nodeList * v1 . NodeList , fn func ( node v1 . Node ) bool ) {
var l [ ] v1 . Node
for _ , node := range nodeList . Items {
if fn ( node ) {
l = append ( l , node )
}
}
nodeList . Items = l
}
// IsNodeSchedulable returns true if:
// 1) doesn't have "unschedulable" field set
// 2) it also returns true from IsNodeReady
func IsNodeSchedulable ( node * v1 . Node ) bool {
if node == nil {
return false
}
return ! node . Spec . Unschedulable && IsNodeReady ( node )
}
// IsNodeReady returns true if:
// 1) it's Ready condition is set to true
// 2) doesn't have NetworkUnavailable condition set to true
func IsNodeReady ( node * v1 . Node ) bool {
nodeReady := IsConditionSetAsExpected ( node , v1 . NodeReady , true )
networkReady := isConditionUnset ( node , v1 . NodeNetworkUnavailable ) ||
IsConditionSetAsExpectedSilent ( node , v1 . NodeNetworkUnavailable , false )
return nodeReady && networkReady
}
// IsConditionSetAsExpected returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue with detailed logging.
func IsConditionSetAsExpected ( node * v1 . Node , conditionType v1 . NodeConditionType , wantTrue bool ) bool {
return isNodeConditionSetAsExpected ( node , conditionType , wantTrue , false )
}
// IsConditionSetAsExpectedSilent returns a wantTrue value if the node has a match to the conditionType, otherwise returns an opposite value of the wantTrue.
func IsConditionSetAsExpectedSilent ( node * v1 . Node , conditionType v1 . NodeConditionType , wantTrue bool ) bool {
return isNodeConditionSetAsExpected ( node , conditionType , wantTrue , true )
}
// isConditionUnset returns true if conditions of the given node do not have a match to the given conditionType, otherwise false.
func isConditionUnset ( node * v1 . Node , conditionType v1 . NodeConditionType ) bool {
for _ , cond := range node . Status . Conditions {
if cond . Type == conditionType {
return false
}
}
return true
}
2020-08-13 15:35:43 -04:00
// isNodeConditionSetAsExpected checks a node for a condition, and returns 'true' if the wanted value is the same as the condition value, useful for polling until a condition on a node is met.
2020-03-18 10:59:51 -04:00
func isNodeConditionSetAsExpected ( node * v1 . Node , conditionType v1 . NodeConditionType , wantTrue , silent bool ) bool {
// Check the node readiness condition (logging all).
for _ , cond := range node . Status . Conditions {
// Ensure that the condition type and the status matches as desired.
if cond . Type == conditionType {
// For NodeReady condition we need to check Taints as well
if cond . Type == v1 . NodeReady {
hasNodeControllerTaints := false
// For NodeReady we need to check if Taints are gone as well
taints := node . Spec . Taints
for _ , taint := range taints {
if taint . MatchTaint ( nodectlr . UnreachableTaintTemplate ) || taint . MatchTaint ( nodectlr . NotReadyTaintTemplate ) {
hasNodeControllerTaints = true
break
}
}
if wantTrue {
if ( cond . Status == v1 . ConditionTrue ) && ! hasNodeControllerTaints {
return true
}
msg := ""
if ! hasNodeControllerTaints {
msg = fmt . Sprintf ( "Condition %s of node %s is %v instead of %t. Reason: %v, message: %v" ,
conditionType , node . Name , cond . Status == v1 . ConditionTrue , wantTrue , cond . Reason , cond . Message )
} else {
msg = fmt . Sprintf ( "Condition %s of node %s is %v, but Node is tainted by NodeController with %v. Failure" ,
conditionType , node . Name , cond . Status == v1 . ConditionTrue , taints )
}
if ! silent {
klog . Infof ( msg )
}
return false
}
// TODO: check if the Node is tainted once we enable NC notReady/unreachable taints by default
if cond . Status != v1 . ConditionTrue {
return true
}
if ! silent {
klog . Infof ( "Condition %s of node %s is %v instead of %t. Reason: %v, message: %v" ,
conditionType , node . Name , cond . Status == v1 . ConditionTrue , wantTrue , cond . Reason , cond . Message )
}
return false
}
if ( wantTrue && ( cond . Status == v1 . ConditionTrue ) ) || ( ! wantTrue && ( cond . Status != v1 . ConditionTrue ) ) {
return true
}
if ! silent {
klog . Infof ( "Condition %s of node %s is %v instead of %t. Reason: %v, message: %v" ,
conditionType , node . Name , cond . Status == v1 . ConditionTrue , wantTrue , cond . Reason , cond . Message )
}
return false
}
}
if ! silent {
klog . Infof ( "Couldn't find condition %v on node %v" , conditionType , node . Name )
}
return false
}