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 (
2020-02-07 21:16:47 -05:00
"context"
2015-05-21 21:14:26 -04:00
"fmt"
2019-11-08 12:55:28 -05:00
"io/ioutil"
2018-10-12 09:05:34 -04:00
"math/rand"
2019-11-08 12:55:28 -05:00
"path"
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
2019-12-11 16:05:32 -05:00
"k8s.io/apimachinery/pkg/runtime"
2019-08-25 03:20:50 -04:00
v1 "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"
"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"
2015-05-21 21:14:26 -04:00
2019-03-25 18:07:14 -04:00
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
2019-11-13 14:34:08 -05:00
// TODO: Remove the following imports (ref: https://github.com/kubernetes/kubernetes/issues/81245)
e2emetrics "k8s.io/kubernetes/test/e2e/framework/metrics"
2015-05-21 21:14:26 -04:00
)
2016-02-09 16:36:48 -05:00
const (
2019-03-25 18:07:14 -04:00
// DefaultNamespaceDeletionTimeout is timeout duration for waiting for a namespace deletion.
2019-10-02 22:48:06 -04:00
DefaultNamespaceDeletionTimeout = 5 * 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
2019-12-11 16:05:32 -05:00
clientConfig * rest . Config
2017-08-09 09:54:16 -04:00
ClientSet clientset . Interface
KubemarkExternalClusterClientSet clientset . Interface
2016-09-22 11:00:19 -04:00
2019-04-01 18:05:54 -04:00
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
2019-12-11 15:53:00 -05:00
// afterEaches is a map of name to function to be called after each test. These are not
// cleared. The call order is randomized so that no dependencies can grow between
// the various afterEaches
afterEaches map [ string ] AfterEachActionFunc
2020-01-23 23:05:13 -05:00
// beforeEachStarted indicates that BeforeEach has started
beforeEachStarted bool
2016-02-24 10:24:36 -05:00
// configuration for framework's client
2019-04-08 03:11:37 -04:00
Options Options
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-07-02 23:15:20 -04:00
clusterAutoscalerMetricsBeforeTest e2emetrics . Collection
2020-10-22 09:36:13 -04:00
// Timeouts contains the custom timeouts used during the test execution.
Timeouts * TimeoutContext
2015-05-21 21:14:26 -04:00
}
2019-12-11 15:53:00 -05:00
// AfterEachActionFunc is a function that can be called after each test
type AfterEachActionFunc func ( f * Framework , failed bool )
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-04-08 03:11:37 -04:00
// Options is a struct for managing test framework options.
type Options 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
}
2020-10-22 09:36:13 -04:00
// NewFrameworkWithCustomTimeouts makes a framework with with custom timeouts.
func NewFrameworkWithCustomTimeouts ( baseName string , timeouts * TimeoutContext ) * Framework {
f := NewDefaultFramework ( baseName )
f . Timeouts = timeouts
return f
}
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 {
2019-04-08 03:11:37 -04:00
options := Options {
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.
2019-04-08 03:11:37 -04:00
func NewFramework ( baseName string , options Options , 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 ,
2020-10-22 09:36:13 -04:00
Timeouts : NewTimeoutContextWithDefaults ( ) ,
2015-05-21 21:14:26 -04:00
}
2019-12-11 15:53:00 -05:00
f . AddAfterEach ( "dumpNamespaceInfo" , func ( f * Framework , failed bool ) {
if ! failed {
return
}
if ! TestContext . DumpLogsOnFailure {
return
}
if ! f . SkipNamespaceCreation {
2019-12-11 16:00:57 -05:00
for _ , ns := range f . namespacesToDelete {
DumpAllNamespaceInfo ( f . ClientSet , ns . Name )
}
2019-12-11 15:53:00 -05: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 ( ) {
2020-01-23 23:05:13 -05:00
f . beforeEachStarted = true
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-07-18 19:54:28 -04:00
ExpectNoError ( err )
2018-08-17 18:38:19 -04:00
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
}
2019-12-11 16:05:32 -05:00
f . clientConfig = rest . CopyConfig ( config )
2016-11-18 15:55:17 -05:00
f . ClientSet , err = clientset . 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 )
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 {
2019-08-20 20:55:32 -04:00
Logf ( "Skipping waiting for service account" )
2017-04-04 20:21:45 -04:00
}
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 {
2019-08-20 20:55:32 -04:00
Logf ( "Error while creating NewResourceUsageGatherer: %v" , err )
2016-02-23 10:45:42 -05:00
} 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 {
2021-05-17 03:20:11 -04:00
grabber , err := e2emetrics . NewMetricsGrabber ( f . ClientSet , f . KubemarkExternalClusterClientSet , f . ClientConfig ( ) , ! ProviderIs ( "kubemark" ) , false , false , false , TestContext . IncludeClusterAutoscalerMetrics , false )
2017-08-09 09:54:16 -04:00
if err != nil {
2019-08-20 20:55:32 -04:00
Logf ( "Failed to create MetricsGrabber (skipping ClusterAutoscaler metrics gathering before test): %v" , err )
2017-08-09 09:54:16 -04:00
} else {
f . clusterAutoscalerMetricsBeforeTest , err = grabber . Grab ( )
if err != nil {
2019-08-20 20:55:32 -04:00
Logf ( "MetricsGrabber failed to grab CA metrics before test (skipping metrics gathering): %v" , err )
2017-08-09 09:54:16 -04:00
} else {
2019-08-20 20:55:32 -04:00
Logf ( "Gathered ClusterAutoscaler metrics before test" )
2017-08-09 09:54:16 -04:00
}
}
}
2018-07-17 11:22:39 -04:00
f . flakeReport = NewFlakeReport ( )
2015-05-21 21:14:26 -04:00
}
2019-11-08 12:55:28 -05:00
// printSummaries prints summaries of tests.
func printSummaries ( summaries [ ] TestDataSummary , testBaseName string ) {
now := time . Now ( )
for i := range summaries {
Logf ( "Printing summary: %v" , summaries [ i ] . SummaryKind ( ) )
switch TestContext . OutputPrintType {
case "hr" :
if TestContext . ReportDir == "" {
Logf ( summaries [ i ] . PrintHumanReadable ( ) )
} else {
// TODO: learn to extract test name and append it to the kind instead of timestamp.
filePath := path . Join ( TestContext . ReportDir , summaries [ i ] . SummaryKind ( ) + "_" + testBaseName + "_" + now . Format ( time . RFC3339 ) + ".txt" )
if err := ioutil . WriteFile ( filePath , [ ] byte ( summaries [ i ] . PrintHumanReadable ( ) ) , 0644 ) ; err != nil {
Logf ( "Failed to write file %v with test performance data: %v" , filePath , err )
}
}
case "json" :
fallthrough
default :
if TestContext . OutputPrintType != "json" {
Logf ( "Unknown output type: %v. Printing JSON" , TestContext . OutputPrintType )
}
if TestContext . ReportDir == "" {
Logf ( "%v JSON\n%v" , summaries [ i ] . SummaryKind ( ) , summaries [ i ] . PrintJSON ( ) )
Logf ( "Finished" )
} else {
// TODO: learn to extract test name and append it to the kind instead of timestamp.
filePath := path . Join ( TestContext . ReportDir , summaries [ i ] . SummaryKind ( ) + "_" + testBaseName + "_" + now . Format ( time . RFC3339 ) + ".json" )
Logf ( "Writing to %s" , filePath )
if err := ioutil . WriteFile ( filePath , [ ] byte ( summaries [ i ] . PrintJSON ( ) ) , 0644 ) ; err != nil {
Logf ( "Failed to write file %v with test performance data: %v" , filePath , err )
}
}
}
}
}
2019-12-11 15:53:00 -05:00
// AddAfterEach is a way to add a function to be called after every test. The execution order is intentionally random
// to avoid growing dependencies. If you register the same name twice, it is a coding error and will panic.
func ( f * Framework ) AddAfterEach ( name string , fn AfterEachActionFunc ) {
if _ , ok := f . afterEaches [ name ] ; ok {
panic ( fmt . Sprintf ( "%q is already registered" , name ) )
}
if f . afterEaches == nil {
f . afterEaches = map [ string ] AfterEachActionFunc { }
}
f . afterEaches [ name ] = fn
}
2016-04-07 13:21:31 -04:00
// AfterEach deletes the namespace, after reading its events.
func ( f * Framework ) AfterEach ( ) {
2020-01-23 23:05:13 -05:00
// If BeforeEach never started AfterEach should be skipped.
// Currently some tests under e2e/storage have this condition.
if ! f . beforeEachStarted {
return
}
2016-02-06 00:12:33 -05:00
RemoveCleanupAction ( f . cleanupHandle )
2020-01-23 23:05:13 -05:00
// This should not happen. Given ClientSet is a public field a test must have updated it!
// Error out early before any API calls during cleanup.
if f . ClientSet == nil {
Failf ( "The framework ClientSet must not be nil at this point" )
}
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 ) )
2020-03-01 12:24:42 -05:00
if err := f . ClientSet . CoreV1 ( ) . Namespaces ( ) . Delete ( context . TODO ( ) , ns . Name , metav1 . DeleteOptions { } ) ; 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
2019-11-22 06:14:28 -05:00
// Dump namespace if we are unable to delete the namespace and the dump was not already performed.
if ! ginkgo . CurrentGinkgoTestDescription ( ) . Failed && TestContext . DumpLogsOnFailure {
DumpAllNamespaceInfo ( f . ClientSet , ns . Name )
}
2016-02-17 08:52:38 -05:00
} else {
2019-08-20 20:55:32 -04:00
Logf ( "Namespace %v was already deleted" , ns . Name )
2016-02-17 08:52:38 -05:00
}
2016-02-15 04:19:04 -05:00
}
}
} else {
2017-05-03 21:35:39 -04:00
if ! TestContext . DeleteNamespace {
2019-08-20 20:55:32 -04:00
Logf ( "Found DeleteNamespace=false, skipping namespace deletion!" )
2017-05-03 21:35:39 -04:00
} else {
2019-08-20 20:55:32 -04:00
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
2019-12-11 16:05:32 -05:00
f . clientConfig = 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
}
2019-08-20 20:55:32 -04:00
Failf ( strings . Join ( messages , "," ) )
2016-08-29 16:33:55 -04:00
}
2016-02-15 04:19:04 -05:00
} ( )
2019-12-11 15:53:00 -05:00
// run all aftereach functions in random order to ensure no dependencies grow
for _ , afterEachFn := range f . afterEaches {
afterEachFn ( f , ginkgo . CurrentGinkgoTestDescription ( ) . Failed )
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" )
2021-05-17 03:20:11 -04:00
grabber , err := e2emetrics . NewMetricsGrabber ( f . ClientSet , f . KubemarkExternalClusterClientSet , f . ClientConfig ( ) , grabMetricsFromKubelets , true , true , true , TestContext . IncludeClusterAutoscalerMetrics , false )
2016-01-04 10:42:51 -05:00
if err != nil {
2019-08-20 20:55:32 -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 {
2019-08-20 20:55:32 -04:00
Logf ( "MetricsGrabber failed to grab some of the metrics: %v" , err )
2016-01-04 10:42:51 -05:00
}
2019-08-07 03:08:18 -04:00
( * e2emetrics . ComponentCollection ) ( & received ) . ComputeClusterAutoscalerMetricsDelta ( f . clusterAutoscalerMetricsBeforeTest )
f . TestSummaries = append ( f . TestSummaries , ( * e2emetrics . ComponentCollection ) ( & 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
}
2019-11-08 12:55:28 -05: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 {
2019-08-20 20:55:32 -04:00
Failf ( "All nodes should be ready after test, %v" , err )
2016-02-11 07:14:32 -05:00
}
2015-05-21 21:14:26 -04:00
}
2020-08-27 17:54:07 -04:00
// DeleteNamespace can be used to delete a namespace. Additionally it can be used to
// dump namespace information so as it can be used as an alternative of framework
// deleting the namespace towards the end.
func ( f * Framework ) DeleteNamespace ( name string ) {
defer func ( ) {
err := f . ClientSet . CoreV1 ( ) . Namespaces ( ) . Delete ( context . TODO ( ) , name , metav1 . DeleteOptions { } )
if err != nil && ! apierrors . IsNotFound ( err ) {
Logf ( "error deleting namespace %s: %v" , name , err )
return
}
err = WaitForNamespacesDeleted ( f . ClientSet , [ ] string { name } , DefaultNamespaceDeletionTimeout )
if err != nil {
Logf ( "error deleting namespace %s: %v" , name , err )
return
}
// remove deleted namespace from namespacesToDelete map
for i , ns := range f . namespacesToDelete {
if ns == nil {
continue
}
if ns . Name == name {
f . namespacesToDelete = append ( f . namespacesToDelete [ : i ] , f . namespacesToDelete [ i + 1 : ] ... )
}
}
} ( )
// if current test failed then we should dump namespace information
if ! f . SkipNamespaceCreation && ginkgo . CurrentGinkgoTestDescription ( ) . Failed && TestContext . DumpLogsOnFailure {
DumpAllNamespaceInfo ( f . ClientSet , name )
}
}
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-10-29 05:32:42 -04:00
CreatePrivilegedPSPBinding ( f . ClientSet , 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-02-19 14:27:25 -05:00
}
2019-12-11 16:05:32 -05:00
// ClientConfig an externally accessible method for reading the kube client config.
func ( f * Framework ) ClientConfig ( ) * rest . Config {
ret := rest . CopyConfig ( f . clientConfig )
// json is least common denominator
ret . ContentType = runtime . ContentTypeJSON
ret . AcceptContentTypes = runtime . ContentTypeJSON
return ret
}
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
// 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" `
2020-10-29 13:08:32 -04:00
Password string ` yaml:"password" datapolicy:"password" `
Token string ` yaml:"token" datapolicy:"token" `
2016-05-10 17:44:45 -04:00
} ` 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
}
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 ( ) }
2020-02-07 21:16:47 -05:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( context . TODO ( ) , options )
2016-03-31 14:45:08 -04:00
} else {
2020-02-07 21:16:47 -05:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( context . TODO ( ) , 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.
2019-08-20 20:55:32 -04:00
Logf ( "Selector matched %v pods for %v" , len ( pl . Items ) , p . Selectors )
2016-03-31 14:45:08 -04:00
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 {
2019-08-20 20:55:32 -04:00
Logf ( "Error detected on %v : %v !" , pod . Name , err )
2016-03-31 14:45:08 -04:00
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 {
2019-08-20 20:55:32 -04:00
Logf ( "Cutting polling short: We got an error from the pod filtering layer." )
2016-03-31 14:45:08 -04:00
// 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
}
2019-08-20 20:55:32 -04:00
Logf ( "Found %v / %v" , len ( pods ) , atLeast )
2016-03-31 14:45:08 -04:00
// Success
if len ( pods ) >= atLeast {
return true , nil
}
// Keep trying...
return false , nil
} )
2019-08-20 20:55:32 -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 {
2019-08-20 20:55:32 -04:00
Failf ( "Verified %v of %v pods , error : %v" , len ( pods ) , atLeast , err )
2016-03-31 14:45:08 -04:00
}
}
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 {
2019-08-20 20:55:32 -04:00
Failf ( "No pods matched the filter." )
2016-05-12 13:02:36 -04:00
}
2019-08-20 20:55:32 -04:00
Logf ( "ForEach: Found %v pods from the filter. Now looping through them." , len ( pods ) )
2016-03-31 14:45:08 -04:00
for _ , p := range pods {
podFunc ( p )
}
} else {
2019-08-20 20:55:32 -04:00
Logf ( "ForEach: Something went wrong when filtering pods to execute against: %v" , err )
2016-03-31 14:45:08 -04:00
}
return err
}