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
"net/http/httptest"
2020-03-18 10:59:51 -04:00
"strings"
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/fields"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
2021-01-26 14:03:08 -05:00
v1helper "k8s.io/component-helpers/scheduling/corev1"
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
)
2018-11-13 22:28:00 -05:00
// CreateTestingNamespace creates a namespace for testing.
2017-11-12 04:40:57 -05:00
func CreateTestingNamespace ( baseName string , apiserver * httptest . Server , t * testing . T ) * v1 . Namespace {
// TODO: Create a namespace with a given basename.
// Currently we neither create the namespace nor delete all of its contents at the end.
// But as long as tests are not using the same namespaces, this should work fine.
return & v1 . Namespace {
ObjectMeta : metav1 . ObjectMeta {
// TODO: Once we start creating namespaces, switch to GenerateName.
Name : baseName ,
} ,
}
}
2018-11-13 22:28:00 -05:00
// DeleteTestingNamespace is currently a no-op function.
2017-11-12 04:40:57 -05:00
func DeleteTestingNamespace ( ns * v1 . Namespace , apiserver * httptest . Server , t * testing . T ) {
// TODO: Remove all resources from a given namespace once we implement CreateTestingNamespace.
}
2020-03-18 10:59:51 -04:00
// GetReadySchedulableNodes addresses the common use case of getting nodes you can do work on.
// 1) Needs to be schedulable.
// 2) Needs to be ready.
// If EITHER 1 or 2 is not true, most tests will want to ignore the node entirely.
// If there are no nodes that are both ready and schedulable, this will return an error.
func GetReadySchedulableNodes ( c clientset . Interface ) ( nodes * v1 . NodeList , err error ) {
nodes , err = checkWaitListSchedulableNodes ( c )
if err != nil {
return nil , fmt . Errorf ( "listing schedulable nodes error: %s" , err )
}
Filter ( nodes , func ( node v1 . Node ) bool {
return IsNodeSchedulable ( & node ) && isNodeUntainted ( & node )
} )
if len ( nodes . Items ) == 0 {
return nil , fmt . Errorf ( "there are currently no ready, schedulable nodes in the cluster" )
}
return nodes , nil
}
// checkWaitListSchedulableNodes is a wrapper around listing nodes supporting retries.
func checkWaitListSchedulableNodes ( c clientset . Interface ) ( * v1 . NodeList , error ) {
nodes , err := waitListSchedulableNodes ( c )
if err != nil {
return nil , fmt . Errorf ( "error: %s. Non-retryable failure or timed out while listing nodes for integration test cluster" , err )
}
return nodes , nil
}
// waitListSchedulableNodes is a wrapper around listing nodes supporting retries.
func waitListSchedulableNodes ( c clientset . Interface ) ( * v1 . NodeList , error ) {
var nodes * v1 . NodeList
var err error
if wait . PollImmediate ( poll , singleCallTimeout , func ( ) ( bool , error ) {
nodes , err = c . CoreV1 ( ) . Nodes ( ) . List ( context . TODO ( ) , metav1 . ListOptions { FieldSelector : fields . Set {
"spec.unschedulable" : "false" ,
} . AsSelector ( ) . String ( ) } )
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
}
// isNodeUntainted tests whether a fake pod can be scheduled on "node", given its current taints.
// TODO: need to discuss wether to return bool and error type
func isNodeUntainted ( node * v1 . Node ) bool {
nonblockingTaints := ""
fakePod := & v1 . Pod {
TypeMeta : metav1 . TypeMeta {
Kind : "Pod" ,
APIVersion : "v1" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : "fake-not-scheduled" ,
Namespace : "fake-not-scheduled" ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "fake-not-scheduled" ,
Image : "fake-not-scheduled" ,
} ,
} ,
} ,
}
// Simple lookup for nonblocking taints based on comma-delimited list.
nonblockingTaintsMap := map [ string ] struct { } { }
for _ , t := range strings . Split ( nonblockingTaints , "," ) {
if strings . TrimSpace ( t ) != "" {
nonblockingTaintsMap [ strings . TrimSpace ( t ) ] = struct { } { }
}
}
2020-04-10 13:49:39 -04:00
n := node
2020-03-18 10:59:51 -04:00
if len ( nonblockingTaintsMap ) > 0 {
nodeCopy := node . DeepCopy ( )
nodeCopy . Spec . Taints = [ ] v1 . Taint { }
for _ , v := range node . Spec . Taints {
if _ , isNonblockingTaint := nonblockingTaintsMap [ v . Key ] ; ! isNonblockingTaint {
nodeCopy . Spec . Taints = append ( nodeCopy . Spec . Taints , v )
}
}
2020-04-10 13:49:39 -04:00
n = nodeCopy
2020-03-18 10:59:51 -04:00
}
2021-02-02 19:04:02 -05:00
_ , untolerated := v1helper . FindMatchingUntoleratedTaint ( n . Spec . Taints , fakePod . Spec . Tolerations , func ( t * v1 . Taint ) bool {
2020-03-18 10:59:51 -04:00
return t . Effect == v1 . TaintEffectNoExecute || t . Effect == v1 . TaintEffectNoSchedule
} )
2021-02-02 19:04:02 -05:00
return ! untolerated
2020-03-18 10:59:51 -04:00
}