2015-05-21 21:14:26 -04:00
/ *
2016-06-02 20:25:58 -04:00
Copyright 2015 The Kubernetes Authors .
2015-05-21 21:14:26 -04:00
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 .
* /
2018-08-01 06:46:59 -04:00
// Package framework contains provider-independent helper code for
// building and running E2E tests with Ginkgo. The actual Ginkgo test
// suites gets assembled by combining this framework, the optional
// provider support code and specific tests via a separate .go file
// like Kubernetes' test/e2e.go.
2016-04-06 20:47:39 -04:00
package framework
2015-05-21 21:14:26 -04:00
import (
2017-04-20 09:52:34 -04:00
"bufio"
2015-07-19 22:00:10 -04:00
"bytes"
2015-05-21 21:14:26 -04:00
"fmt"
2018-10-12 09:05:34 -04:00
"math/rand"
2017-04-20 09:52:34 -04:00
"os"
2015-07-19 22:00:10 -04:00
"strings"
2015-11-26 09:43:30 -05:00
"sync"
2015-07-09 14:38:10 -04:00
"time"
2015-05-21 21:14:26 -04:00
2017-06-22 14:24:23 -04:00
"k8s.io/api/core/v1"
2017-01-13 12:48:50 -05:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2017-01-11 09:09:48 -05:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
2017-01-27 15:42:17 -05:00
"k8s.io/apimachinery/pkg/util/intstr"
2017-01-11 09:09:48 -05:00
"k8s.io/apimachinery/pkg/util/wait"
2018-01-04 08:52:25 -05:00
"k8s.io/client-go/discovery"
2018-12-19 16:34:37 -05:00
cacheddiscovery "k8s.io/client-go/discovery/cached/memory"
2017-01-25 14:00:30 -05:00
"k8s.io/client-go/dynamic"
2017-06-23 16:56:37 -04:00
clientset "k8s.io/client-go/kubernetes"
2019-04-02 10:08:55 -04:00
"k8s.io/client-go/kubernetes/scheme"
2018-01-04 08:52:25 -05:00
"k8s.io/client-go/rest"
2018-05-07 15:41:13 -04:00
"k8s.io/client-go/restmapper"
2018-01-04 08:52:25 -05:00
scaleclient "k8s.io/client-go/scale"
2017-07-31 18:43:20 -04:00
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
2017-08-07 03:57:47 -04:00
"k8s.io/kubernetes/test/e2e/framework/metrics"
2016-10-12 07:37:37 -04:00
testutils "k8s.io/kubernetes/test/utils"
2015-05-21 21:14:26 -04:00
2019-03-25 18:07:14 -04:00
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
2015-05-21 21:14:26 -04:00
)
2016-02-09 16:36:48 -05:00
const (
2017-06-13 21:20:29 -04:00
maxKubectlExecRetries = 5
2019-03-25 18:07:14 -04:00
// DefaultNamespaceDeletionTimeout is timeout duration for waiting for a namespace deletion.
2017-06-13 21:20:29 -04:00
// TODO(mikedanese): reset this to 5 minutes once #47135 is resolved.
// ref https://github.com/kubernetes/kubernetes/issues/47135
DefaultNamespaceDeletionTimeout = 10 * time . Minute
2016-02-09 16:36:48 -05:00
)
2015-05-21 21:14:26 -04:00
// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
// Eventual goal is to merge this with integration test framework.
type Framework struct {
BaseName string
2018-10-12 09:05:34 -04:00
// Set together with creating the ClientSet and the namespace.
// Guaranteed to be unique in the cluster even when running the same
// test multiple times in parallel.
UniqueName string
2017-08-09 09:54:16 -04:00
ClientSet clientset . Interface
KubemarkExternalClusterClientSet clientset . Interface
2016-09-22 11:00:19 -04:00
2019-03-22 15:09:07 -04:00
AggregatorClient * aggregatorclient . Clientset
DynamicClient dynamic . Interface
2016-02-06 00:38:52 -05:00
2018-01-04 08:52:25 -05:00
ScalesGetter scaleclient . ScalesGetter
2017-04-04 20:21:45 -04:00
SkipNamespaceCreation bool // Whether to skip creating a namespace
Namespace * v1 . Namespace // Every test has at least one namespace unless creation is skipped
2016-11-18 15:55:17 -05:00
namespacesToDelete [ ] * v1 . Namespace // Some tests have more than one.
2015-09-24 04:02:07 -04:00
NamespaceDeletionTimeout time . Duration
2017-10-31 20:15:11 -04:00
SkipPrivilegedPSPBinding bool // Whether to skip creating a binding to the privileged PSP in the test namespace
2015-10-29 10:49:23 -04:00
2018-01-26 17:30:30 -05:00
gatherer * ContainerResourceGatherer
2016-02-12 14:33:32 -05:00
// Constraints that passed to a check which is executed after data is gathered to
2017-03-31 02:57:11 -04:00
// see if 99% of results are within acceptable bounds. It has to be injected in the test,
// as expectations vary greatly. Constraints are grouped by the container names.
2016-04-07 13:21:31 -04:00
AddonResourceConstraints map [ string ] ResourceConstraint
2015-11-26 09:43:30 -05:00
logsSizeWaitGroup sync . WaitGroup
logsSizeCloseChannel chan bool
logsSizeVerifier * LogsSizeVerifier
2016-02-06 00:12:33 -05:00
2018-07-17 11:22:39 -04:00
// Flaky operation failures in an e2e test can be captured through this.
flakeReport * FlakeReport
2016-02-06 00:12:33 -05:00
// To make sure that this framework cleans up after itself, no matter what,
2016-04-07 13:21:31 -04:00
// we install a Cleanup action before each test and clear it after. If we
// should abort, the AfterSuite hook should run all Cleanup actions.
2016-02-06 00:12:33 -05:00
cleanupHandle CleanupActionHandle
2016-02-24 10:24:36 -05:00
// configuration for framework's client
2017-01-24 14:54:29 -05:00
Options FrameworkOptions
2017-05-10 08:50:38 -04:00
// Place where various additional data is stored during test run to be printed to ReportDir,
// or stdout if ReportDir is not set once test ends.
TestSummaries [ ] TestDataSummary
2017-07-20 09:05:25 -04:00
2017-08-09 09:54:16 -04:00
// Place to keep ClusterAutoscaler metrics from before test in order to compute delta.
2019-02-12 20:46:04 -05:00
clusterAutoscalerMetricsBeforeTest metrics . Collection
2015-05-21 21:14:26 -04:00
}
2019-03-25 18:07:14 -04:00
// TestDataSummary is an interface for managing test data.
2015-12-29 03:19:54 -05:00
type TestDataSummary interface {
2017-04-19 08:53:56 -04:00
SummaryKind ( ) string
2015-12-29 03:19:54 -05:00
PrintHumanReadable ( ) string
PrintJSON ( ) string
}
2019-03-25 18:07:14 -04:00
// FrameworkOptions is a struct for managing test framework options.
2016-02-24 10:24:36 -05:00
type FrameworkOptions struct {
2016-08-08 10:36:46 -04:00
ClientQPS float32
ClientBurst int
2016-11-20 21:55:31 -05:00
GroupVersion * schema . GroupVersion
2016-02-24 10:24:36 -05:00
}
2019-03-25 18:07:14 -04:00
// NewDefaultFramework makes a new framework and sets up a BeforeEach/AfterEach for
2015-05-21 21:14:26 -04:00
// you (you can write additional before/after each functions).
2016-02-24 10:24:36 -05:00
func NewDefaultFramework ( baseName string ) * Framework {
options := FrameworkOptions {
2016-04-07 13:21:31 -04:00
ClientQPS : 20 ,
ClientBurst : 50 ,
2016-02-24 10:24:36 -05:00
}
2016-04-18 16:12:19 -04:00
return NewFramework ( baseName , options , nil )
2016-02-24 10:24:36 -05:00
}
2019-03-25 18:07:14 -04:00
// NewFramework creates a test framework.
2016-11-18 15:55:17 -05:00
func NewFramework ( baseName string , options FrameworkOptions , client clientset . Interface ) * Framework {
2015-05-21 21:14:26 -04:00
f := & Framework {
2015-11-30 09:29:40 -05:00
BaseName : baseName ,
2016-04-07 13:21:31 -04:00
AddonResourceConstraints : make ( map [ string ] ResourceConstraint ) ,
2017-01-24 14:54:29 -05:00
Options : options ,
2016-10-18 09:00:38 -04:00
ClientSet : client ,
2015-05-21 21:14:26 -04:00
}
2019-03-25 18:07:14 -04:00
ginkgo . BeforeEach ( f . BeforeEach )
ginkgo . AfterEach ( f . AfterEach )
2015-05-21 21:14:26 -04:00
return f
}
2016-04-07 13:21:31 -04:00
// BeforeEach gets a client and makes a namespace.
func ( f * Framework ) BeforeEach ( ) {
2016-02-06 00:12:33 -05:00
// The fact that we need this feels like a bug in ginkgo.
// https://github.com/onsi/ginkgo/issues/222
2016-04-07 13:21:31 -04:00
f . cleanupHandle = AddCleanupAction ( f . AfterEach )
2016-10-18 09:00:38 -04:00
if f . ClientSet == nil {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "Creating a kubernetes client" )
2016-07-22 17:55:50 -04:00
config , err := LoadConfig ( )
2019-03-25 18:07:14 -04:00
testDesc := ginkgo . CurrentGinkgoTestDescription ( )
2018-08-17 18:38:19 -04:00
if len ( testDesc . ComponentTexts ) > 0 {
componentTexts := strings . Join ( testDesc . ComponentTexts , " " )
config . UserAgent = fmt . Sprintf (
"%v -- %v" ,
rest . DefaultKubernetesUserAgent ( ) ,
componentTexts )
}
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2017-01-24 14:54:29 -05:00
config . QPS = f . Options . ClientQPS
config . Burst = f . Options . ClientBurst
if f . Options . GroupVersion != nil {
config . GroupVersion = f . Options . GroupVersion
2016-08-08 10:36:46 -04:00
}
2016-05-09 04:57:26 -04:00
if TestContext . KubeAPIContentType != "" {
config . ContentType = TestContext . KubeAPIContentType
}
2016-11-18 15:55:17 -05:00
f . ClientSet , err = clientset . NewForConfig ( config )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2017-07-31 18:43:20 -04:00
f . AggregatorClient , err = aggregatorclient . NewForConfig ( config )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2018-04-26 04:53:07 -04:00
f . DynamicClient , err = dynamic . NewForConfig ( config )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2019-02-20 00:44:17 -05:00
// node.k8s.io is based on CRD, which is served only as JSON
2018-08-28 09:27:41 -04:00
jsonConfig := config
jsonConfig . ContentType = "application/json"
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2018-01-04 08:52:25 -05:00
// create scales getter, set GroupVersion and NegotiatedSerializer to default values
// as they are required when creating a REST client.
if config . GroupVersion == nil {
config . GroupVersion = & schema . GroupVersion { }
}
if config . NegotiatedSerializer == nil {
2019-04-02 10:08:55 -04:00
config . NegotiatedSerializer = scheme . Codecs
2018-01-04 08:52:25 -05:00
}
restClient , err := rest . RESTClientFor ( config )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2018-01-04 08:52:25 -05:00
discoClient , err := discovery . NewDiscoveryClientForConfig ( config )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2018-01-04 08:52:25 -05:00
cachedDiscoClient := cacheddiscovery . NewMemCacheClient ( discoClient )
2018-05-07 15:41:13 -04:00
restMapper := restmapper . NewDeferredDiscoveryRESTMapper ( cachedDiscoClient )
2018-01-30 11:50:47 -05:00
restMapper . Reset ( )
2018-01-04 08:52:25 -05:00
resolver := scaleclient . NewDiscoveryScaleKindResolver ( cachedDiscoClient )
f . ScalesGetter = scaleclient . New ( restClient , restMapper , dynamic . LegacyAPIPathResolverFunc , resolver )
2018-08-01 06:46:59 -04:00
TestContext . CloudConfig . Provider . FrameworkBeforeEach ( f )
2016-04-18 16:12:19 -04:00
}
2016-05-10 17:44:45 -04:00
2017-04-04 20:21:45 -04:00
if ! f . SkipNamespaceCreation {
2019-03-25 18:07:14 -04:00
ginkgo . By ( fmt . Sprintf ( "Building a namespace api object, basename %s" , f . BaseName ) )
2017-04-04 20:21:45 -04:00
namespace , err := f . CreateNamespace ( f . BaseName , map [ string ] string {
"e2e-framework" : f . BaseName ,
} )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2015-05-21 21:14:26 -04:00
2017-04-04 20:21:45 -04:00
f . Namespace = namespace
2015-05-22 16:46:52 -04:00
2017-04-04 20:21:45 -04:00
if TestContext . VerifyServiceAccount {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "Waiting for a default service account to be provisioned in namespace" )
2017-04-04 20:21:45 -04:00
err = WaitForDefaultServiceAccountInNamespace ( f . ClientSet , namespace . Name )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2017-04-04 20:21:45 -04:00
} else {
Logf ( "Skipping waiting for service account" )
}
2018-10-12 09:05:34 -04:00
f . UniqueName = f . Namespace . GetName ( )
} else {
// not guaranteed to be unique, but very likely
f . UniqueName = fmt . Sprintf ( "%s-%08x" , f . BaseName , rand . Int31 ( ) )
2015-09-15 12:22:12 -04:00
}
2015-10-29 10:49:23 -04:00
2016-05-12 03:49:17 -04:00
if TestContext . GatherKubeSystemResourceUsageData != "false" && TestContext . GatherKubeSystemResourceUsageData != "none" {
2017-04-04 20:21:45 -04:00
var err error
2018-10-01 10:00:16 -04:00
var nodeMode NodesSet
switch TestContext . GatherKubeSystemResourceUsageData {
case "master" :
nodeMode = MasterNodes
case "masteranddns" :
nodeMode = MasterAndDNSNodes
default :
nodeMode = AllNodes
}
2016-10-18 09:00:38 -04:00
f . gatherer , err = NewResourceUsageGatherer ( f . ClientSet , ResourceGathererOptions {
2018-10-05 15:59:38 -04:00
InKubemark : ProviderIs ( "kubemark" ) ,
Nodes : nodeMode ,
2017-09-27 01:27:30 -04:00
ResourceDataGatheringPeriod : 60 * time . Second ,
2017-11-15 17:15:28 -05:00
ProbeDuration : 15 * time . Second ,
2017-11-17 06:58:13 -05:00
PrintVerboseLogs : false ,
2017-09-27 01:27:30 -04:00
} , nil )
2016-02-23 10:45:42 -05:00
if err != nil {
Logf ( "Error while creating NewResourceUsageGatherer: %v" , err )
} else {
2017-09-27 01:27:30 -04:00
go f . gatherer . StartGatheringData ( )
2016-02-23 10:45:42 -05:00
}
2015-10-29 10:49:23 -04:00
}
2015-11-26 09:43:30 -05:00
2016-04-07 13:21:31 -04:00
if TestContext . GatherLogsSizes {
2015-11-26 09:43:30 -05:00
f . logsSizeWaitGroup = sync . WaitGroup { }
f . logsSizeWaitGroup . Add ( 1 )
f . logsSizeCloseChannel = make ( chan bool )
2016-10-18 09:00:38 -04:00
f . logsSizeVerifier = NewLogsVerifier ( f . ClientSet , f . logsSizeCloseChannel )
2015-11-26 09:43:30 -05:00
go func ( ) {
f . logsSizeVerifier . Run ( )
f . logsSizeWaitGroup . Done ( )
} ( )
}
2017-08-09 09:54:16 -04:00
gatherMetricsAfterTest := TestContext . GatherMetricsAfterTest == "true" || TestContext . GatherMetricsAfterTest == "master"
if gatherMetricsAfterTest && TestContext . IncludeClusterAutoscalerMetrics {
grabber , err := metrics . NewMetricsGrabber ( f . ClientSet , f . KubemarkExternalClusterClientSet , ! ProviderIs ( "kubemark" ) , false , false , false , TestContext . IncludeClusterAutoscalerMetrics )
if err != nil {
Logf ( "Failed to create MetricsGrabber (skipping ClusterAutoscaler metrics gathering before test): %v" , err )
} else {
f . clusterAutoscalerMetricsBeforeTest , err = grabber . Grab ( )
if err != nil {
Logf ( "MetricsGrabber failed to grab CA metrics before test (skipping metrics gathering): %v" , err )
} else {
Logf ( "Gathered ClusterAutoscaler metrics before test" )
}
}
}
2018-07-17 11:22:39 -04:00
f . flakeReport = NewFlakeReport ( )
2015-05-21 21:14:26 -04:00
}
2016-04-07 13:21:31 -04:00
// AfterEach deletes the namespace, after reading its events.
func ( f * Framework ) AfterEach ( ) {
2016-02-06 00:12:33 -05:00
RemoveCleanupAction ( f . cleanupHandle )
2016-02-15 04:19:04 -05:00
// DeleteNamespace at the very end in defer, to avoid any
// expectation failures preventing deleting the namespace.
defer func ( ) {
2016-08-29 16:33:55 -04:00
nsDeletionErrors := map [ string ] error { }
2016-10-03 19:39:55 -04:00
// Whether to delete namespace is determined by 3 factors: delete-namespace flag, delete-namespace-on-failure flag and the test result
// if delete-namespace set to false, namespace will always be preserved.
// if delete-namespace is true and delete-namespace-on-failure is false, namespace will be preserved if test failed.
2019-03-25 18:07:14 -04:00
if TestContext . DeleteNamespace && ( TestContext . DeleteNamespaceOnFailure || ! ginkgo . CurrentGinkgoTestDescription ( ) . Failed ) {
2016-02-15 04:19:04 -05:00
for _ , ns := range f . namespacesToDelete {
2019-03-25 18:07:14 -04:00
ginkgo . By ( fmt . Sprintf ( "Destroying namespace %q for this suite." , ns . Name ) )
2017-04-28 17:09:26 -04:00
timeout := DefaultNamespaceDeletionTimeout
2016-02-15 04:19:04 -05:00
if f . NamespaceDeletionTimeout != 0 {
timeout = f . NamespaceDeletionTimeout
}
2018-04-26 04:53:07 -04:00
if err := deleteNS ( f . ClientSet , f . DynamicClient , ns . Name , timeout ) ; err != nil {
2017-01-13 12:48:50 -05:00
if ! apierrors . IsNotFound ( err ) {
2016-08-29 16:33:55 -04:00
nsDeletionErrors [ ns . Name ] = err
2016-02-17 08:52:38 -05:00
} else {
Logf ( "Namespace %v was already deleted" , ns . Name )
}
2016-02-15 04:19:04 -05:00
}
}
} else {
2017-05-03 21:35:39 -04:00
if ! TestContext . DeleteNamespace {
2016-10-03 19:39:55 -04:00
Logf ( "Found DeleteNamespace=false, skipping namespace deletion!" )
2017-05-03 21:35:39 -04:00
} else {
Logf ( "Found DeleteNamespaceOnFailure=false and current test failed, skipping namespace deletion!" )
2016-10-03 19:39:55 -04:00
}
2016-02-15 04:19:04 -05:00
}
// Paranoia-- prevent reuse!
f . Namespace = nil
2016-10-18 09:00:38 -04:00
f . ClientSet = nil
2016-08-29 16:33:55 -04:00
f . namespacesToDelete = nil
// if we had errors deleting, report them now.
if len ( nsDeletionErrors ) != 0 {
messages := [ ] string { }
for namespaceKey , namespaceErr := range nsDeletionErrors {
2016-09-02 19:00:54 -04:00
messages = append ( messages , fmt . Sprintf ( "Couldn't delete ns: %q: %s (%#v)" , namespaceKey , namespaceErr , namespaceErr ) )
2016-08-29 16:33:55 -04:00
}
Failf ( strings . Join ( messages , "," ) )
}
2016-02-15 04:19:04 -05:00
} ( )
2015-05-21 21:14:26 -04:00
// Print events if the test failed.
2019-03-25 18:07:14 -04:00
if ginkgo . CurrentGinkgoTestDescription ( ) . Failed && TestContext . DumpLogsOnFailure {
2018-10-11 07:25:55 -04:00
// Pass both unversioned client and versioned clientset, till we have removed all uses of the unversioned client.
2017-04-04 20:21:45 -04:00
if ! f . SkipNamespaceCreation {
DumpAllNamespaceInfo ( f . ClientSet , f . Namespace . Name )
}
2015-05-21 21:14:26 -04:00
}
2016-05-12 03:49:17 -04:00
if TestContext . GatherKubeSystemResourceUsageData != "false" && TestContext . GatherKubeSystemResourceUsageData != "none" && f . gatherer != nil {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "Collecting resource usage data" )
2017-09-27 01:27:30 -04:00
summary , resourceViolationError := f . gatherer . StopAndSummarize ( [ ] int { 90 , 99 , 100 } , f . AddonResourceConstraints )
2016-12-09 05:55:40 -05:00
defer ExpectNoError ( resourceViolationError )
2017-05-10 08:50:38 -04:00
f . TestSummaries = append ( f . TestSummaries , summary )
2015-10-29 10:49:23 -04:00
}
2015-11-26 09:43:30 -05:00
2016-04-07 13:21:31 -04:00
if TestContext . GatherLogsSizes {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "Gathering log sizes data" )
2015-11-26 09:43:30 -05:00
close ( f . logsSizeCloseChannel )
f . logsSizeWaitGroup . Wait ( )
2017-05-10 08:50:38 -04:00
f . TestSummaries = append ( f . TestSummaries , f . logsSizeVerifier . GetSummary ( ) )
2015-12-29 03:19:54 -05:00
}
2017-08-01 05:55:13 -04:00
if TestContext . GatherMetricsAfterTest != "false" {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "Gathering metrics" )
2017-08-01 05:55:13 -04:00
// Grab apiserver, scheduler, controller-manager metrics and (optionally) nodes' kubelet metrics.
grabMetricsFromKubelets := TestContext . GatherMetricsAfterTest != "master" && ! ProviderIs ( "kubemark" )
2017-08-09 09:54:16 -04:00
grabber , err := metrics . NewMetricsGrabber ( f . ClientSet , f . KubemarkExternalClusterClientSet , grabMetricsFromKubelets , true , true , true , TestContext . IncludeClusterAutoscalerMetrics )
2016-01-04 10:42:51 -05:00
if err != nil {
2017-04-27 12:37:52 -04:00
Logf ( "Failed to create MetricsGrabber (skipping metrics gathering): %v" , err )
2016-01-04 10:42:51 -05:00
} else {
2016-06-13 04:37:21 -04:00
received , err := grabber . Grab ( )
2016-01-04 10:42:51 -05:00
if err != nil {
2017-08-09 09:54:16 -04:00
Logf ( "MetricsGrabber failed to grab some of the metrics: %v" , err )
2016-01-04 10:42:51 -05:00
}
2017-08-09 09:54:16 -04:00
( * MetricsForE2E ) ( & received ) . computeClusterAutoscalerMetricsDelta ( f . clusterAutoscalerMetricsBeforeTest )
f . TestSummaries = append ( f . TestSummaries , ( * MetricsForE2E ) ( & received ) )
2016-01-04 10:42:51 -05:00
}
}
2018-08-01 06:46:59 -04:00
TestContext . CloudConfig . Provider . FrameworkAfterEach ( f )
2017-07-20 09:05:25 -04:00
2018-07-17 11:22:39 -04:00
// Report any flakes that were observed in the e2e test and reset.
if f . flakeReport != nil && f . flakeReport . GetFlakeCount ( ) > 0 {
f . TestSummaries = append ( f . TestSummaries , f . flakeReport )
f . flakeReport = nil
}
2017-05-10 08:50:38 -04:00
PrintSummaries ( f . TestSummaries , f . BaseName )
2015-12-23 09:56:56 -05:00
2016-02-11 07:14:32 -05:00
// Check whether all nodes are ready after the test.
// This is explicitly done at the very end of the test, to avoid
// e.g. not removing namespace in case of this failure.
2016-10-18 09:00:38 -04:00
if err := AllNodesReady ( f . ClientSet , 3 * time . Minute ) ; err != nil {
2016-02-11 07:14:32 -05:00
Failf ( "All nodes should be ready after test, %v" , err )
}
2015-05-21 21:14:26 -04:00
}
2019-03-25 18:07:14 -04:00
// CreateNamespace creates a namespace for e2e testing.
2016-11-18 15:55:17 -05:00
func ( f * Framework ) CreateNamespace ( baseName string , labels map [ string ] string ) ( * v1 . Namespace , error ) {
2016-04-07 13:21:31 -04:00
createTestingNS := TestContext . CreateTestingNS
2016-02-08 08:25:14 -05:00
if createTestingNS == nil {
createTestingNS = CreateTestingNS
}
2016-10-18 09:00:38 -04:00
ns , err := createTestingNS ( baseName , f . ClientSet , labels )
2016-11-03 07:55:46 -04:00
// check ns instead of err to see if it's nil as we may
// fail to create serviceAccount in it.
2018-04-15 20:15:01 -04:00
f . AddNamespacesToDelete ( ns )
2017-10-31 20:15:11 -04:00
2017-12-12 13:20:56 -05:00
if err == nil && ! f . SkipPrivilegedPSPBinding {
2019-03-29 13:55:19 -04:00
createPrivilegedPSPBinding ( f , ns . Name )
2017-10-31 20:15:11 -04:00
}
2016-02-06 00:38:52 -05:00
return ns , err
}
2019-03-25 18:07:14 -04:00
// RecordFlakeIfError records flakeness info if error happens.
// NOTE: This function is not used at any places yet, but we are in progress for https://github.com/kubernetes/kubernetes/issues/66239 which requires this. Please don't remove this.
2018-07-17 11:22:39 -04:00
func ( f * Framework ) RecordFlakeIfError ( err error , optionalDescription ... interface { } ) {
f . flakeReport . RecordFlakeIfError ( err , optionalDescription )
}
2018-04-15 20:15:01 -04:00
// AddNamespacesToDelete adds one or more namespaces to be deleted when the test
// completes.
func ( f * Framework ) AddNamespacesToDelete ( namespaces ... * v1 . Namespace ) {
for _ , ns := range namespaces {
if ns == nil {
continue
}
f . namespacesToDelete = append ( f . namespacesToDelete , ns )
}
}
2016-01-26 15:52:14 -05:00
// WaitForPodTerminated waits for the pod to be terminated with the given reason.
func ( f * Framework ) WaitForPodTerminated ( podName , reason string ) error {
2016-10-18 09:00:38 -04:00
return waitForPodTerminatedInNamespace ( f . ClientSet , podName , reason , f . Namespace . Name )
2016-01-26 15:52:14 -05:00
}
2017-10-06 13:07:29 -04:00
// WaitForPodNotFound waits for the pod to be completely terminated (not "Get-able").
func ( f * Framework ) WaitForPodNotFound ( podName string , timeout time . Duration ) error {
return waitForPodNotFoundInNamespace ( f . ClientSet , podName , f . Namespace . Name , timeout )
}
2015-05-21 21:14:26 -04:00
// WaitForPodRunning waits for the pod to run in the namespace.
func ( f * Framework ) WaitForPodRunning ( podName string ) error {
2016-10-18 09:00:38 -04:00
return WaitForPodNameRunningInNamespace ( f . ClientSet , podName , f . Namespace . Name )
2015-05-21 21:14:26 -04:00
}
2015-05-22 16:12:55 -04:00
2015-12-14 13:21:21 -05:00
// WaitForPodReady waits for the pod to flip to ready in the namespace.
func ( f * Framework ) WaitForPodReady ( podName string ) error {
2017-02-08 13:41:45 -05:00
return waitTimeoutForPodReadyInNamespace ( f . ClientSet , podName , f . Namespace . Name , PodStartTimeout )
2015-12-14 13:21:21 -05:00
}
2015-11-03 18:57:37 -05:00
// WaitForPodRunningSlow waits for the pod to run in the namespace.
// It has a longer timeout then WaitForPodRunning (util.slowPodStartTimeout).
func ( f * Framework ) WaitForPodRunningSlow ( podName string ) error {
2017-02-08 13:41:45 -05:00
return waitForPodRunningInNamespaceSlow ( f . ClientSet , podName , f . Namespace . Name )
2015-11-03 18:57:37 -05:00
}
2016-02-19 14:27:25 -05:00
// WaitForPodNoLongerRunning waits for the pod to no longer be running in the namespace, for either
// success or failure.
func ( f * Framework ) WaitForPodNoLongerRunning ( podName string ) error {
2017-02-08 13:41:45 -05:00
return WaitForPodNoLongerRunningInNamespace ( f . ClientSet , podName , f . Namespace . Name )
2016-02-19 14:27:25 -05:00
}
2016-07-20 14:03:05 -04:00
// TestContainerOutput runs the given pod in the given namespace and waits
// for all of the containers in the podSpec to move into the 'Success' status, and tests
// the specified container log against the given expected output using a substring matcher.
2016-11-18 15:55:17 -05:00
func ( f * Framework ) TestContainerOutput ( scenarioName string , pod * v1 . Pod , containerIndex int , expectedOutput [ ] string ) {
2019-03-25 18:07:14 -04:00
f . testContainerOutputMatcher ( scenarioName , pod , containerIndex , expectedOutput , gomega . ContainSubstring )
2015-10-13 15:51:37 -04:00
}
2016-07-20 14:03:05 -04:00
// TestContainerOutputRegexp runs the given pod in the given namespace and waits
// for all of the containers in the podSpec to move into the 'Success' status, and tests
// the specified container log against the given expected output using a regexp matcher.
2016-11-18 15:55:17 -05:00
func ( f * Framework ) TestContainerOutputRegexp ( scenarioName string , pod * v1 . Pod , containerIndex int , expectedOutput [ ] string ) {
2019-03-25 18:07:14 -04:00
f . testContainerOutputMatcher ( scenarioName , pod , containerIndex , expectedOutput , gomega . MatchRegexp )
2015-05-22 16:12:55 -04:00
}
2015-06-11 18:55:25 -04:00
2019-03-25 18:07:14 -04:00
// WriteFileViaContainer writes a file using kubectl exec echo <contents> > <path> via specified container
// because of the primitive technique we're using here, we only allow ASCII alphanumeric characters
2015-07-19 22:00:10 -04:00
func ( f * Framework ) WriteFileViaContainer ( podName , containerName string , path string , contents string ) error {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "writing a file in the container" )
2015-07-19 22:00:10 -04:00
allowedCharacters := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for _ , c := range contents {
if ! strings . ContainsRune ( allowedCharacters , c ) {
return fmt . Errorf ( "Unsupported character in string to write: %v" , c )
}
}
command := fmt . Sprintf ( "echo '%s' > '%s'" , contents , path )
2016-02-09 16:36:48 -05:00
stdout , stderr , err := kubectlExecWithRetry ( f . Namespace . Name , podName , containerName , "--" , "/bin/sh" , "-c" , command )
2015-07-19 22:00:10 -04:00
if err != nil {
Logf ( "error running kubectl exec to write file: %v\nstdout=%v\nstderr=%v)" , err , string ( stdout ) , string ( stderr ) )
}
return err
}
2019-03-25 18:07:14 -04:00
// ReadFileViaContainer reads a file using kubectl exec cat <path>.
2015-07-19 22:00:10 -04:00
func ( f * Framework ) ReadFileViaContainer ( podName , containerName string , path string ) ( string , error ) {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "reading a file in the container" )
2015-07-19 22:00:10 -04:00
2016-02-09 16:36:48 -05:00
stdout , stderr , err := kubectlExecWithRetry ( f . Namespace . Name , podName , containerName , "--" , "cat" , path )
2015-07-19 22:00:10 -04:00
if err != nil {
Logf ( "error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)" , err , string ( stdout ) , string ( stderr ) )
}
return string ( stdout ) , err
}
2019-03-25 18:07:14 -04:00
// CheckFileSizeViaContainer returns the list of file size under the specified path.
2016-10-03 18:44:40 -04:00
func ( f * Framework ) CheckFileSizeViaContainer ( podName , containerName , path string ) ( string , error ) {
2019-03-25 18:07:14 -04:00
ginkgo . By ( "checking a file size in the container" )
2016-10-03 18:44:40 -04:00
stdout , stderr , err := kubectlExecWithRetry ( f . Namespace . Name , podName , containerName , "--" , "ls" , "-l" , path )
if err != nil {
Logf ( "error running kubectl exec to read file: %v\nstdout=%v\nstderr=%v)" , err , string ( stdout ) , string ( stderr ) )
}
return string ( stdout ) , err
}
2016-03-17 11:38:53 -04:00
// CreateServiceForSimpleAppWithPods is a convenience wrapper to create a service and its matching pods all at once.
2019-03-25 18:07:14 -04:00
func ( f * Framework ) CreateServiceForSimpleAppWithPods ( contPort int , svcPort int , appName string , podSpec func ( n v1 . Node ) v1 . PodSpec , count int , block bool ) ( * v1 . Service , error ) {
var err error
2016-03-17 11:38:53 -04:00
theService := f . CreateServiceForSimpleApp ( contPort , svcPort , appName )
f . CreatePodsPerNodeForSimpleApp ( appName , podSpec , count )
if block {
2016-10-18 09:00:38 -04:00
err = testutils . WaitForPodsWithLabelRunning ( f . ClientSet , f . Namespace . Name , labels . SelectorFromSet ( labels . Set ( theService . Spec . Selector ) ) )
2016-03-17 11:38:53 -04:00
}
2019-03-25 18:07:14 -04:00
return theService , err
2016-03-17 11:38:53 -04:00
}
// CreateServiceForSimpleApp returns a service that selects/exposes pods (send -1 ports if no exposure needed) with an app label.
2016-11-18 15:55:17 -05:00
func ( f * Framework ) CreateServiceForSimpleApp ( contPort , svcPort int , appName string ) * v1 . Service {
2016-03-17 11:38:53 -04:00
if appName == "" {
panic ( fmt . Sprintf ( "no app name provided" ) )
}
serviceSelector := map [ string ] string {
"app" : appName + "-pod" ,
}
// For convenience, user sending ports are optional.
2016-11-18 15:55:17 -05:00
portsFunc := func ( ) [ ] v1 . ServicePort {
2016-03-17 11:38:53 -04:00
if contPort < 1 || svcPort < 1 {
return nil
}
2019-03-25 18:07:14 -04:00
return [ ] v1 . ServicePort { {
Protocol : v1 . ProtocolTCP ,
Port : int32 ( svcPort ) ,
TargetPort : intstr . FromInt ( contPort ) ,
} }
2016-03-17 11:38:53 -04:00
}
Logf ( "Creating a service-for-%v for selecting app=%v-pod" , appName , appName )
2017-10-25 11:54:32 -04:00
service , err := f . ClientSet . CoreV1 ( ) . Services ( f . Namespace . Name ) . Create ( & v1 . Service {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2016-03-17 11:38:53 -04:00
Name : "service-for-" + appName ,
Labels : map [ string ] string {
"app" : appName + "-service" ,
} ,
} ,
2016-11-18 15:55:17 -05:00
Spec : v1 . ServiceSpec {
2016-03-17 11:38:53 -04:00
Ports : portsFunc ( ) ,
Selector : serviceSelector ,
} ,
} )
ExpectNoError ( err )
return service
}
2019-03-25 18:07:14 -04:00
// CreatePodsPerNodeForSimpleApp creates pods w/ labels. Useful for tests which make a bunch of pods w/o any networking.
2016-11-18 15:55:17 -05:00
func ( f * Framework ) CreatePodsPerNodeForSimpleApp ( appName string , podSpec func ( n v1 . Node ) v1 . PodSpec , maxCount int ) map [ string ] string {
2016-10-19 09:55:39 -04:00
nodes := GetReadySchedulableNodesOrDie ( f . ClientSet )
2016-03-17 11:38:53 -04:00
labels := map [ string ] string {
"app" : appName + "-pod" ,
}
for i , node := range nodes . Items {
// one per node, but no more than maxCount.
if i <= maxCount {
Logf ( "%v/%v : Creating container with label app=%v-pod" , i , maxCount , appName )
2017-10-25 11:54:32 -04:00
_ , err := f . ClientSet . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Create ( & v1 . Pod {
2017-01-16 22:38:19 -05:00
ObjectMeta : metav1 . ObjectMeta {
2016-03-17 11:38:53 -04:00
Name : fmt . Sprintf ( appName + "-pod-%v" , i ) ,
Labels : labels ,
} ,
Spec : podSpec ( node ) ,
} )
ExpectNoError ( err )
}
}
return labels
}
2019-03-25 18:07:14 -04:00
// KubeUser is a struct for managing kubernetes user info.
2016-05-10 17:44:45 -04:00
type KubeUser struct {
Name string ` yaml:"name" `
User struct {
Username string ` yaml:"username" `
Password string ` yaml:"password" `
Token string ` yaml:"token" `
} ` yaml:"user" `
}
2019-03-25 18:07:14 -04:00
// KubeCluster is a struct for managing kubernetes cluster info.
2016-05-10 17:44:45 -04:00
type KubeCluster struct {
Name string ` yaml:"name" `
Cluster struct {
CertificateAuthorityData string ` yaml:"certificate-authority-data" `
Server string ` yaml:"server" `
} ` yaml:"cluster" `
}
2019-03-25 18:07:14 -04:00
// KubeConfig is a struct for managing kubernetes config.
2016-05-10 17:44:45 -04:00
type KubeConfig struct {
Contexts [ ] struct {
Name string ` yaml:"name" `
Context struct {
Cluster string ` yaml:"cluster" `
User string
} ` yaml:"context" `
} ` yaml:"contexts" `
Clusters [ ] KubeCluster ` yaml:"clusters" `
Users [ ] KubeUser ` yaml:"users" `
}
2019-03-25 18:07:14 -04:00
// FindUser returns user info which is the specified user name.
2017-01-11 08:23:31 -05:00
func ( kc * KubeConfig ) FindUser ( name string ) * KubeUser {
2016-05-10 17:44:45 -04:00
for _ , user := range kc . Users {
if user . Name == name {
return & user
}
}
return nil
}
2019-03-25 18:07:14 -04:00
// FindCluster returns cluster info which is the specified cluster name.
2017-01-11 08:23:31 -05:00
func ( kc * KubeConfig ) FindCluster ( name string ) * KubeCluster {
2016-05-10 17:44:45 -04:00
for _ , cluster := range kc . Clusters {
if cluster . Name == name {
return & cluster
}
}
return nil
}
2016-02-09 16:36:48 -05:00
func kubectlExecWithRetry ( namespace string , podName , containerName string , args ... string ) ( [ ] byte , [ ] byte , error ) {
for numRetries := 0 ; numRetries < maxKubectlExecRetries ; numRetries ++ {
if numRetries > 0 {
Logf ( "Retrying kubectl exec (retry count=%v/%v)" , numRetries + 1 , maxKubectlExecRetries )
}
stdOutBytes , stdErrBytes , err := kubectlExec ( namespace , podName , containerName , args ... )
if err != nil {
if strings . Contains ( strings . ToLower ( string ( stdErrBytes ) ) , "i/o timeout" ) {
// Retry on "i/o timeout" errors
Logf ( "Warning: kubectl exec encountered i/o timeout.\nerr=%v\nstdout=%v\nstderr=%v)" , err , string ( stdOutBytes ) , string ( stdErrBytes ) )
continue
}
2016-05-23 15:46:37 -04:00
if strings . Contains ( strings . ToLower ( string ( stdErrBytes ) ) , "container not found" ) {
// Retry on "container not found" errors
Logf ( "Warning: kubectl exec encountered container not found.\nerr=%v\nstdout=%v\nstderr=%v)" , err , string ( stdOutBytes ) , string ( stdErrBytes ) )
time . Sleep ( 2 * time . Second )
continue
}
2016-02-09 16:36:48 -05:00
}
return stdOutBytes , stdErrBytes , err
}
err := fmt . Errorf ( "Failed: kubectl exec failed %d times with \"i/o timeout\". Giving up." , maxKubectlExecRetries )
return nil , nil , err
}
2015-07-19 22:00:10 -04:00
func kubectlExec ( namespace string , podName , containerName string , args ... string ) ( [ ] byte , [ ] byte , error ) {
var stdout , stderr bytes . Buffer
cmdArgs := [ ] string {
"exec" ,
fmt . Sprintf ( "--namespace=%v" , namespace ) ,
podName ,
fmt . Sprintf ( "-c=%v" , containerName ) ,
}
cmdArgs = append ( cmdArgs , args ... )
2016-04-07 13:21:31 -04:00
cmd := KubectlCmd ( cmdArgs ... )
2015-07-19 22:00:10 -04:00
cmd . Stdout , cmd . Stderr = & stdout , & stderr
2016-05-23 08:39:34 -04:00
Logf ( "Running '%s %s'" , cmd . Path , strings . Join ( cmdArgs , " " ) )
2015-07-19 22:00:10 -04:00
err := cmd . Run ( )
return stdout . Bytes ( ) , stderr . Bytes ( ) , err
}
2016-03-11 14:29:44 -05:00
2019-03-25 18:07:14 -04:00
// KubeDescribe is wrapper function for ginkgo describe. Adds namespacing.
2016-03-11 14:29:44 -05:00
// TODO: Support type safe tagging as well https://github.com/kubernetes/kubernetes/pull/22401.
func KubeDescribe ( text string , body func ( ) ) bool {
2019-03-25 18:07:14 -04:00
return ginkgo . Describe ( "[k8s.io] " + text , body )
2016-03-11 14:29:44 -05:00
}
2016-03-31 14:45:08 -04:00
2019-03-25 18:07:14 -04:00
// ConformanceIt is wrapper function for ginkgo It. Adds "[Conformance]" tag and makes static analysis easier.
2017-10-26 13:46:09 -04:00
func ConformanceIt ( text string , body interface { } , timeout ... float64 ) bool {
2019-03-25 18:07:14 -04:00
return ginkgo . It ( text + " [Conformance]" , body , timeout ... )
2017-10-26 13:46:09 -04:00
}
2016-03-31 14:45:08 -04:00
// PodStateVerification represents a verification of pod state.
// Any time you have a set of pods that you want to operate against or query,
// this struct can be used to declaratively identify those pods.
type PodStateVerification struct {
// Optional: only pods that have k=v labels will pass this filter.
Selectors map [ string ] string
// Required: The phases which are valid for your pod.
2016-11-18 15:55:17 -05:00
ValidPhases [ ] v1 . PodPhase
2016-03-31 14:45:08 -04:00
// Optional: only pods passing this function will pass the filter
// Verify a pod.
2019-02-15 05:11:29 -05:00
// As an optimization, in addition to specifying filter (boolean),
2016-03-31 14:45:08 -04:00
// this function allows specifying an error as well.
// The error indicates that the polling of the pod spectrum should stop.
2016-11-18 15:55:17 -05:00
Verify func ( v1 . Pod ) ( bool , error )
2016-03-31 14:45:08 -04:00
// Optional: only pods with this name will pass the filter.
PodName string
}
2019-03-25 18:07:14 -04:00
// ClusterVerification is a struct for a verification of cluster state.
2016-03-31 14:45:08 -04:00
type ClusterVerification struct {
2016-11-18 15:55:17 -05:00
client clientset . Interface
namespace * v1 . Namespace // pointer rather than string, since ns isn't created until before each.
2016-03-31 14:45:08 -04:00
podState PodStateVerification
}
2019-03-25 18:07:14 -04:00
// NewClusterVerification creates a new cluster verification.
2016-12-05 16:27:07 -05:00
func ( f * Framework ) NewClusterVerification ( namespace * v1 . Namespace , filter PodStateVerification ) * ClusterVerification {
2016-03-31 14:45:08 -04:00
return & ClusterVerification {
2016-10-18 09:00:38 -04:00
f . ClientSet ,
2016-12-05 16:27:07 -05:00
namespace ,
2016-03-31 14:45:08 -04:00
filter ,
}
}
2016-11-18 15:55:17 -05:00
func passesPodNameFilter ( pod v1 . Pod , name string ) bool {
2016-03-31 14:45:08 -04:00
return name == "" || strings . Contains ( pod . Name , name )
}
2016-11-18 15:55:17 -05:00
func passesVerifyFilter ( pod v1 . Pod , verify func ( p v1 . Pod ) ( bool , error ) ) ( bool , error ) {
2016-03-31 14:45:08 -04:00
if verify == nil {
return true , nil
}
2019-03-25 18:07:14 -04:00
verified , err := verify ( pod )
// If an error is returned, by definition, pod verification fails
if err != nil {
return false , err
}
return verified , nil
2016-03-31 14:45:08 -04:00
}
2016-11-18 15:55:17 -05:00
func passesPhasesFilter ( pod v1 . Pod , validPhases [ ] v1 . PodPhase ) bool {
2016-03-31 14:45:08 -04:00
passesPhaseFilter := false
for _ , phase := range validPhases {
if pod . Status . Phase == phase {
passesPhaseFilter = true
}
}
return passesPhaseFilter
}
// filterLabels returns a list of pods which have labels.
2016-11-18 15:55:17 -05:00
func filterLabels ( selectors map [ string ] string , cli clientset . Interface , ns string ) ( * v1 . PodList , error ) {
2016-03-31 14:45:08 -04:00
var err error
var selector labels . Selector
2016-11-18 15:55:17 -05:00
var pl * v1 . PodList
2016-03-31 14:45:08 -04:00
// List pods based on selectors. This might be a tiny optimization rather then filtering
// everything manually.
if len ( selectors ) > 0 {
selector = labels . SelectorFromSet ( labels . Set ( selectors ) )
2017-01-21 22:36:02 -05:00
options := metav1 . ListOptions { LabelSelector : selector . String ( ) }
2017-10-25 11:54:32 -04:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( options )
2016-03-31 14:45:08 -04:00
} else {
2017-10-25 11:54:32 -04:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( metav1 . ListOptions { } )
2016-03-31 14:45:08 -04:00
}
return pl , err
}
// filter filters pods which pass a filter. It can be used to compose
// the more useful abstractions like ForEach, WaitFor, and so on, which
// can be used directly by tests.
2016-11-18 15:55:17 -05:00
func ( p * PodStateVerification ) filter ( c clientset . Interface , namespace * v1 . Namespace ) ( [ ] v1 . Pod , error ) {
2016-03-31 14:45:08 -04:00
if len ( p . ValidPhases ) == 0 || namespace == nil {
panic ( fmt . Errorf ( "Need to specify a valid pod phases (%v) and namespace (%v). " , p . ValidPhases , namespace ) )
}
ns := namespace . Name
2016-11-18 15:55:17 -05:00
pl , err := filterLabels ( p . Selectors , c , ns ) // Build an v1.PodList to operate against.
2016-03-31 14:45:08 -04:00
Logf ( "Selector matched %v pods for %v" , len ( pl . Items ) , p . Selectors )
if len ( pl . Items ) == 0 || err != nil {
return pl . Items , err
}
unfilteredPods := pl . Items
2016-11-18 15:55:17 -05:00
filteredPods := [ ] v1 . Pod { }
2016-03-31 14:45:08 -04:00
ReturnPodsSoFar :
// Next: Pod must match at least one of the states that the user specified
for _ , pod := range unfilteredPods {
if ! ( passesPhasesFilter ( pod , p . ValidPhases ) && passesPodNameFilter ( pod , p . PodName ) ) {
continue
}
passesVerify , err := passesVerifyFilter ( pod , p . Verify )
if err != nil {
Logf ( "Error detected on %v : %v !" , pod . Name , err )
break ReturnPodsSoFar
}
if passesVerify {
filteredPods = append ( filteredPods , pod )
}
}
return filteredPods , err
}
// WaitFor waits for some minimum number of pods to be verified, according to the PodStateVerification
// definition.
2016-11-18 15:55:17 -05:00
func ( cl * ClusterVerification ) WaitFor ( atLeast int , timeout time . Duration ) ( [ ] v1 . Pod , error ) {
pods := [ ] v1 . Pod { }
2016-03-31 14:45:08 -04:00
var returnedErr error
err := wait . Poll ( 1 * time . Second , timeout , func ( ) ( bool , error ) {
pods , returnedErr = cl . podState . filter ( cl . client , cl . namespace )
// Failure
if returnedErr != nil {
Logf ( "Cutting polling short: We got an error from the pod filtering layer." )
// stop polling if the pod filtering returns an error. that should never happen.
// it indicates, for example, that the client is broken or something non-pod related.
return false , returnedErr
}
Logf ( "Found %v / %v" , len ( pods ) , atLeast )
// Success
if len ( pods ) >= atLeast {
return true , nil
}
// Keep trying...
return false , nil
} )
2016-04-15 15:18:27 -04:00
Logf ( "WaitFor completed with timeout %v. Pods found = %v out of %v" , timeout , len ( pods ) , atLeast )
2016-03-31 14:45:08 -04:00
return pods , err
}
// WaitForOrFail provides a shorthand WaitFor with failure as an option if anything goes wrong.
func ( cl * ClusterVerification ) WaitForOrFail ( atLeast int , timeout time . Duration ) {
pods , err := cl . WaitFor ( atLeast , timeout )
if err != nil || len ( pods ) < atLeast {
Failf ( "Verified %v of %v pods , error : %v" , len ( pods ) , atLeast , err )
}
}
2019-02-15 05:11:29 -05:00
// ForEach runs a function against every verifiable pod. Be warned that this doesn't wait for "n" pods to verify,
2016-03-31 14:45:08 -04:00
// so it may return very quickly if you have strict pod state requirements.
//
// For example, if you require at least 5 pods to be running before your test will pass,
// its smart to first call "clusterVerification.WaitFor(5)" before you call clusterVerification.ForEach.
2016-11-18 15:55:17 -05:00
func ( cl * ClusterVerification ) ForEach ( podFunc func ( v1 . Pod ) ) error {
2016-03-31 14:45:08 -04:00
pods , err := cl . podState . filter ( cl . client , cl . namespace )
if err == nil {
2016-05-12 13:02:36 -04:00
if len ( pods ) == 0 {
Failf ( "No pods matched the filter." )
}
2016-03-31 14:45:08 -04:00
Logf ( "ForEach: Found %v pods from the filter. Now looping through them." , len ( pods ) )
for _ , p := range pods {
podFunc ( p )
}
} else {
Logf ( "ForEach: Something went wrong when filtering pods to execute against: %v" , err )
}
return err
}
2017-04-20 09:52:34 -04:00
// GetLogToFileFunc is a convenience function that returns a function that have the same interface as
// Logf, but writes to a specified file.
func GetLogToFileFunc ( file * os . File ) func ( format string , args ... interface { } ) {
return func ( format string , args ... interface { } ) {
writer := bufio . NewWriter ( file )
if _ , err := fmt . Fprintf ( writer , format , args ... ) ; err != nil {
Logf ( "Failed to write file %v with test performance data: %v" , file . Name ( ) , err )
}
writer . Flush ( )
}
}