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"
2018-10-12 09:05:34 -04:00
"math/rand"
2021-10-29 18:41:02 -04:00
"os"
2019-11-08 12:55:28 -05:00
"path"
2023-01-03 11:01:33 -05:00
"reflect"
2015-07-19 22:00:10 -04:00
"strings"
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"
2023-02-28 03:40:51 -05:00
v1svc "k8s.io/client-go/applyconfigurations/core/v1"
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"
2022-01-30 07:36:21 -05:00
admissionapi "k8s.io/pod-security-admission/api"
2015-05-21 21:14:26 -04:00
2022-03-29 02:12:12 -04:00
"github.com/onsi/ginkgo/v2"
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
2023-02-28 03:40:51 -05:00
defaultServiceAccountName = "default"
2016-02-09 16:36:48 -05:00
)
2022-08-25 11:04:05 -04:00
var (
// NewFrameworkExtensions lists functions that get called by
// NewFramework after constructing a new framework and after
// calling ginkgo.BeforeEach for the framework.
//
// This can be used by extensions of the core framework to modify
// settings in the framework instance or to add additional callbacks
2023-12-19 20:31:45 -05:00
// with ginkgo.BeforeEach/AfterEach/DeferCleanup.
2022-08-25 11:04:05 -04:00
//
// When a test runs, functions will be invoked in this order:
2022-10-17 04:27:14 -04:00
// - BeforeEaches defined by tests before f.NewDefaultFramework
// in the order in which they were defined (first-in-first-out)
2022-08-25 11:04:05 -04:00
// - f.BeforeEach
2022-10-17 04:27:14 -04:00
// - BeforeEaches defined by tests after f.NewDefaultFramework
2022-08-25 11:04:05 -04:00
// - It callback
// - all AfterEaches in the order in which they were defined
// - all DeferCleanups with the order reversed (first-in-last-out)
// - f.AfterEach
2022-10-17 04:27:14 -04:00
//
// Because a test might skip test execution in a BeforeEach that runs
// before f.BeforeEach, AfterEach callbacks that depend on the
// framework instance must check whether it was initialized. They can
// do that by checking f.ClientSet for nil. DeferCleanup callbacks
// don't need to do this because they get defined when the test
// runs.
2022-08-25 11:04:05 -04:00
NewFrameworkExtensions [ ] func ( f * Framework )
)
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.
2023-05-10 09:25:50 -04:00
//
// You can configure the pod security level for your test by setting the `NamespacePodSecurityLevel`
// which will set all three of pod security admission enforce, warn and audit labels on the namespace.
// The default pod security profile is "restricted".
// Each of the labels can be overridden by using more specific NamespacePodSecurity* attributes of this
// struct.
2015-05-21 21:14:26 -04:00
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
2022-01-30 07:36:21 -05:00
SkipNamespaceCreation bool // Whether to skip creating a namespace
2023-02-28 03:40:51 -05:00
SkipSecretCreation bool // Whether to skip creating secret for a test
2022-01-30 07:36:21 -05:00
Namespace * v1 . Namespace // Every test has at least one namespace unless creation is skipped
namespacesToDelete [ ] * v1 . Namespace // Some tests have more than one.
NamespaceDeletionTimeout time . Duration
NamespacePodSecurityEnforceLevel admissionapi . Level // The pod security enforcement level for namespaces to be applied.
2023-05-10 09:25:50 -04:00
NamespacePodSecurityWarnLevel admissionapi . Level // The pod security warn (client logging) level for namespaces to be applied.
NamespacePodSecurityAuditLevel admissionapi . Level // The pod security audit (server logging) level for namespaces to be applied.
NamespacePodSecurityLevel admissionapi . Level // The pod security level to be used for all of enforcement, warn and audit. Can be rewritten by more specific configuration attributes.
2015-10-29 10:49:23 -04: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-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
2020-10-22 09:36:13 -04:00
// Timeouts contains the custom timeouts used during the test execution.
Timeouts * TimeoutContext
2022-08-25 07:43:04 -04:00
// DumpAllNamespaceInfo is invoked by the framework to record
// information about a namespace after a test failure.
DumpAllNamespaceInfo DumpAllNamespaceInfoAction
2015-05-21 21:14:26 -04:00
}
2022-08-25 07:43:04 -04:00
// DumpAllNamespaceInfoAction is called after each failed test for namespaces
// created for the test.
2022-12-12 04:11:10 -05:00
type DumpAllNamespaceInfoAction func ( ctx context . Context , f * Framework , namespace string )
2022-08-25 07:43:04 -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-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
}
2023-01-03 11:01:33 -05:00
// NewFrameworkWithCustomTimeouts makes a framework with custom timeouts.
// For timeout values that are zero the normal default value continues to
// be used.
2020-10-22 09:36:13 -04:00
func NewFrameworkWithCustomTimeouts ( baseName string , timeouts * TimeoutContext ) * Framework {
f := NewDefaultFramework ( baseName )
2023-01-03 11:01:33 -05:00
in := reflect . ValueOf ( timeouts ) . Elem ( )
out := reflect . ValueOf ( f . Timeouts ) . Elem ( )
for i := 0 ; i < in . NumField ( ) ; i ++ {
value := in . Field ( i )
if ! value . IsZero ( ) {
out . Field ( i ) . Set ( value )
}
}
2020-10-22 09:36:13 -04:00
return f
}
2022-08-24 02:44:26 -04:00
// NewDefaultFramework makes a new framework and sets up a BeforeEach which
// initializes the framework instance. It cleans up with a DeferCleanup,
// which runs last, so a AfterEach in the test still has a valid framework
// instance.
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 {
2022-08-25 12:19:16 -04:00
BaseName : baseName ,
Options : options ,
ClientSet : client ,
2023-01-03 11:28:28 -05:00
Timeouts : NewTimeoutContext ( ) ,
2015-05-21 21:14:26 -04:00
}
2022-08-25 11:04:05 -04:00
// The order is important here: if the extension calls ginkgo.BeforeEach
// itself, then it can be sure that f.BeforeEach already ran when its
// own callback gets invoked.
2022-09-13 08:27:32 -04:00
ginkgo . BeforeEach ( f . BeforeEach , AnnotatedLocation ( "set up framework" ) )
2022-08-25 11:04:05 -04:00
for _ , extension := range NewFrameworkExtensions {
extension ( f )
}
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.
2022-12-12 04:11:10 -05:00
func ( f * Framework ) BeforeEach ( ctx context . Context ) {
2022-09-16 07:28:13 -04:00
// DeferCleanup, in contrast to AfterEach, triggers execution in
2022-08-24 02:44:26 -04:00
// first-in-last-out order. This ensures that the framework instance
// remains valid as long as possible.
//
// In addition, AfterEach will not be called if a test never gets here.
2022-09-13 08:27:32 -04:00
ginkgo . DeferCleanup ( f . AfterEach , AnnotatedLocation ( "tear down framework" ) )
2020-01-23 23:05:13 -05:00
2022-08-26 06:31:19 -04:00
// Registered later and thus runs before deleting namespaces.
2022-09-13 08:27:32 -04:00
ginkgo . DeferCleanup ( f . dumpNamespaceInfo , AnnotatedLocation ( "dump namespaces" ) )
2022-08-26 06:31:19 -04:00
2022-09-06 10:54:39 -04:00
ginkgo . By ( "Creating a kubernetes client" )
config , err := LoadConfig ( )
ExpectNoError ( err )
2018-08-17 18:38:19 -04:00
2022-09-06 10:54:39 -04:00
config . QPS = f . Options . ClientQPS
config . Burst = f . Options . ClientBurst
if f . Options . GroupVersion != nil {
config . GroupVersion = f . Options . GroupVersion
2016-04-18 16:12:19 -04:00
}
2022-09-06 10:54:39 -04:00
if TestContext . KubeAPIContentType != "" {
config . ContentType = TestContext . KubeAPIContentType
}
f . clientConfig = rest . CopyConfig ( config )
f . ClientSet , err = clientset . NewForConfig ( config )
ExpectNoError ( err )
f . DynamicClient , err = dynamic . NewForConfig ( config )
ExpectNoError ( err )
// 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 {
config . NegotiatedSerializer = scheme . Codecs
}
restClient , err := rest . RESTClientFor ( config )
ExpectNoError ( err )
discoClient , err := discovery . NewDiscoveryClientForConfig ( config )
ExpectNoError ( err )
cachedDiscoClient := cacheddiscovery . NewMemCacheClient ( discoClient )
restMapper := restmapper . NewDeferredDiscoveryRESTMapper ( cachedDiscoClient )
restMapper . Reset ( )
resolver := scaleclient . NewDiscoveryScaleKindResolver ( cachedDiscoClient )
f . ScalesGetter = scaleclient . New ( restClient , restMapper , dynamic . LegacyAPIPathResolverFunc , resolver )
TestContext . CloudConfig . Provider . FrameworkBeforeEach ( f )
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 ) )
2022-12-12 04:11:10 -05:00
namespace , err := f . CreateNamespace ( ctx , f . BaseName , map [ string ] string {
2017-04-04 20:21:45 -04:00
"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" )
2022-12-12 04:11:10 -05:00
err = WaitForDefaultServiceAccountInNamespace ( ctx , f . ClientSet , namespace . Name )
2019-02-14 23:10:36 -05:00
ExpectNoError ( err )
2022-01-25 13:11:52 -05:00
ginkgo . By ( "Waiting for kube-root-ca.crt to be provisioned in namespace" )
2022-12-12 04:11:10 -05:00
err = WaitForKubeRootCAInNamespace ( ctx , f . ClientSet , namespace . Name )
2022-01-25 13:11:52 -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
}
2023-02-28 03:40:51 -05: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
2018-07-17 11:22:39 -04:00
f . flakeReport = NewFlakeReport ( )
2015-05-21 21:14:26 -04:00
}
2022-12-12 04:11:10 -05:00
func ( f * Framework ) dumpNamespaceInfo ( ctx context . Context ) {
2022-08-26 06:31:19 -04:00
if ! ginkgo . CurrentSpecReport ( ) . Failed ( ) {
return
}
if ! TestContext . DumpLogsOnFailure {
return
}
2022-11-02 13:07:47 -04:00
if f . DumpAllNamespaceInfo == nil {
return
}
2022-08-26 06:31:19 -04:00
ginkgo . By ( "dump namespace information after failure" , func ( ) {
if ! f . SkipNamespaceCreation {
for _ , ns := range f . namespacesToDelete {
2022-12-12 04:11:10 -05:00
f . DumpAllNamespaceInfo ( ctx , f , ns . Name )
2022-08-26 06:31:19 -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" )
2021-10-29 18:41:02 -04:00
if err := os . WriteFile ( filePath , [ ] byte ( summaries [ i ] . PrintHumanReadable ( ) ) , 0644 ) ; err != nil {
2019-11-08 12:55:28 -05:00
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 )
2021-10-29 18:41:02 -04:00
if err := os . WriteFile ( filePath , [ ] byte ( summaries [ i ] . PrintJSON ( ) ) , 0644 ) ; err != nil {
2019-11-08 12:55:28 -05:00
Logf ( "Failed to write file %v with test performance data: %v" , filePath , err )
}
}
}
}
}
2016-04-07 13:21:31 -04:00
// AfterEach deletes the namespace, after reading its events.
2022-12-12 04:11:10 -05:00
func ( f * Framework ) AfterEach ( ctx context . Context ) {
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.
2022-04-24 04:55:25 -04:00
if TestContext . DeleteNamespace && ( TestContext . DeleteNamespaceOnFailure || ! ginkgo . CurrentSpecReport ( ) . 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 ) )
2022-12-12 04:11:10 -05:00
if err := f . ClientSet . CoreV1 ( ) . Namespaces ( ) . Delete ( ctx , 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.
2022-08-25 07:43:04 -04:00
if ! ginkgo . CurrentSpecReport ( ) . Failed ( ) && TestContext . DumpLogsOnFailure && f . DumpAllNamespaceInfo != nil {
2022-12-12 04:11:10 -05:00
f . DumpAllNamespaceInfo ( ctx , f , ns . Name )
2019-11-22 06:14:28 -05:00
}
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
}
2022-10-17 04:27:14 -04:00
// Unsetting this is relevant for a following test that uses
// the same instance because it might not reach f.BeforeEach
// when some other BeforeEach skips the test first.
2016-02-15 04:19:04 -05:00
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
} ( )
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-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.
2022-12-12 04:11:10 -05:00
func ( f * Framework ) DeleteNamespace ( ctx context . Context , name string ) {
2020-08-27 17:54:07 -04:00
defer func ( ) {
2022-12-12 04:11:10 -05:00
err := f . ClientSet . CoreV1 ( ) . Namespaces ( ) . Delete ( ctx , name , metav1 . DeleteOptions { } )
2020-08-27 17:54:07 -04:00
if err != nil && ! apierrors . IsNotFound ( err ) {
Logf ( "error deleting namespace %s: %v" , name , err )
return
}
2022-12-12 04:11:10 -05:00
err = WaitForNamespacesDeleted ( ctx , f . ClientSet , [ ] string { name } , DefaultNamespaceDeletionTimeout )
2020-08-27 17:54:07 -04:00
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
2022-08-25 07:43:04 -04:00
if ! f . SkipNamespaceCreation && ginkgo . CurrentSpecReport ( ) . Failed ( ) && TestContext . DumpLogsOnFailure && f . DumpAllNamespaceInfo != nil {
2022-12-12 04:11:10 -05:00
f . DumpAllNamespaceInfo ( ctx , f , name )
2020-08-27 17:54:07 -04:00
}
}
2019-03-25 18:07:14 -04:00
// CreateNamespace creates a namespace for e2e testing.
2022-12-12 04:11:10 -05:00
func ( f * Framework ) CreateNamespace ( ctx context . Context , 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
}
2022-01-30 07:36:21 -05:00
if labels == nil {
labels = make ( map [ string ] string )
} else {
labelsCopy := make ( map [ string ] string )
for k , v := range labels {
labelsCopy [ k ] = v
}
labels = labelsCopy
}
2023-05-10 09:25:50 -04:00
labels [ admissionapi . EnforceLevelLabel ] = firstNonEmptyPSaLevelOrRestricted ( f . NamespacePodSecurityEnforceLevel , f . NamespacePodSecurityLevel )
labels [ admissionapi . WarnLevelLabel ] = firstNonEmptyPSaLevelOrRestricted ( f . NamespacePodSecurityWarnLevel , f . NamespacePodSecurityLevel )
labels [ admissionapi . AuditLevelLabel ] = firstNonEmptyPSaLevelOrRestricted ( f . NamespacePodSecurityAuditLevel , f . NamespacePodSecurityLevel )
2022-01-30 07:36:21 -05:00
2022-12-12 04:11:10 -05:00
ns , err := createTestingNS ( ctx , 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
2023-02-28 03:40:51 -05:00
if TestContext . E2EDockerConfigFile != "" && ! f . SkipSecretCreation {
// With the Secret created, the default service account (in the new namespace)
// is patched with the secret and can then be referenced by all the pods spawned by E2E process, and repository authentication should be successful.
secret , err := f . createSecretFromDockerConfig ( ctx , ns . Name )
if err != nil {
return ns , fmt . Errorf ( "failed to create secret from docker config file: %v" , err )
}
serviceAccountClient := f . ClientSet . CoreV1 ( ) . ServiceAccounts ( ns . Name )
serviceAccountConfig := v1svc . ServiceAccount ( defaultServiceAccountName , ns . Name )
serviceAccountConfig . ImagePullSecrets = append ( serviceAccountConfig . ImagePullSecrets , v1svc . LocalObjectReferenceApplyConfiguration { Name : & secret . Name } )
svc , err := serviceAccountClient . Apply ( ctx , serviceAccountConfig , metav1 . ApplyOptions { FieldManager : "e2e-framework" } )
if err != nil {
return ns , fmt . Errorf ( "failed to patch imagePullSecret [%s] to service account [%s]: %v" , secret . Name , svc . Name , err )
}
}
2016-02-06 00:38:52 -05:00
return ns , err
}
2023-05-10 09:25:50 -04:00
func firstNonEmptyPSaLevelOrRestricted ( levelConfig ... admissionapi . Level ) string {
for _ , l := range levelConfig {
if len ( l ) > 0 {
return string ( l )
}
}
return string ( admissionapi . LevelRestricted )
}
2023-02-28 03:40:51 -05:00
// createSecretFromDockerConfig creates a secret using the private image registry credentials.
// The credentials are provided by --e2e-docker-config-file flag.
func ( f * Framework ) createSecretFromDockerConfig ( ctx context . Context , namespace string ) ( * v1 . Secret , error ) {
contents , err := os . ReadFile ( TestContext . E2EDockerConfigFile )
if err != nil {
return nil , fmt . Errorf ( "error reading docker config file: %v" , err )
}
secretObject := & v1 . Secret {
Data : map [ string ] [ ] byte { v1 . DockerConfigJsonKey : contents } ,
Type : v1 . SecretTypeDockerConfigJson ,
}
secretObject . GenerateName = "registry-cred"
Logf ( "create image pull secret %s" , secretObject . Name )
secret , err := f . ClientSet . CoreV1 ( ) . Secrets ( namespace ) . Create ( ctx , secretObject , metav1 . CreateOptions { } )
return secret , 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 { } ) {
2022-07-09 06:53:54 -04:00
f . flakeReport . RecordFlakeIfError ( err , optionalDescription ... )
2018-07-17 11:22:39 -04:00
}
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
}
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
}
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.
2022-12-12 04:11:10 -05:00
func filterLabels ( ctx context . Context , 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 ( ) }
2022-12-12 04:11:10 -05:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( ctx , options )
2016-03-31 14:45:08 -04:00
} else {
2022-12-12 04:11:10 -05:00
pl , err = cli . CoreV1 ( ) . Pods ( ns ) . List ( ctx , 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.
2022-12-12 04:11:10 -05:00
func ( p * PodStateVerification ) filter ( ctx context . Context , 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
2022-12-12 04:11:10 -05:00
pl , err := filterLabels ( ctx , 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.
2022-12-12 04:11:10 -05:00
func ( cl * ClusterVerification ) WaitFor ( ctx context . Context , atLeast int , timeout time . Duration ) ( [ ] v1 . Pod , error ) {
2016-11-18 15:55:17 -05:00
pods := [ ] v1 . Pod { }
2016-03-31 14:45:08 -04:00
var returnedErr error
2022-12-12 04:11:10 -05:00
err := wait . PollWithContext ( ctx , 1 * time . Second , timeout , func ( ctx context . Context ) ( bool , error ) {
pods , returnedErr = cl . podState . filter ( ctx , cl . client , cl . namespace )
2016-03-31 14:45:08 -04:00
// 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.
2022-12-12 04:11:10 -05:00
func ( cl * ClusterVerification ) WaitForOrFail ( ctx context . Context , atLeast int , timeout time . Duration ) {
pods , err := cl . WaitFor ( ctx , atLeast , timeout )
2016-03-31 14:45:08 -04:00
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.
2022-12-12 04:11:10 -05:00
func ( cl * ClusterVerification ) ForEach ( ctx context . Context , podFunc func ( v1 . Pod ) ) error {
pods , err := cl . podState . filter ( ctx , cl . client , cl . namespace )
2016-03-31 14:45:08 -04:00
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
}