Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use test namespaces with numeric suffix #5337

Merged
merged 10 commits into from
Jun 16, 2021
Merged
1 change: 1 addition & 0 deletions test/conformance/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var brokerClass string
func TestMain(m *testing.M) {
os.Exit(func() int {
test.InitializeEventingFlags()
testlib.ReuseNamespace = test.EventingFlags.ReuseNamespace
channelTestRunner = testlib.ComponentsTestRunner{
ComponentFeatureMap: testlib.ChannelFeatureMap,
ComponentsToTest: test.EventingFlags.Channels,
Expand Down
1 change: 1 addition & 0 deletions test/e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var brokerClass string

func TestMain(m *testing.M) {
test.InitializeEventingFlags()
testlib.ReuseNamespace = test.EventingFlags.ReuseNamespace
channelTestRunner = testlib.ComponentsTestRunner{
ComponentFeatureMap: testlib.ChannelFeatureMap,
ComponentsToTest: test.EventingFlags.Channels,
Expand Down
3 changes: 3 additions & 0 deletions test/e2e_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ func InitializeEventingFlags() {
flag.Var(&EventingFlags.Brokers, "brokers", BrokerUsage)
flag.StringVar(&EventingFlags.BrokerName, "brokername", "", BrokerNameUsage)
flag.StringVar(&EventingFlags.BrokerNamespace, "brokernamespace", "", BrokerNamespaceUsage)
// Might be useful in restricted environments where namespaces need to be
// created by a user with increased privileges (admin).
flag.BoolVar(&EventingFlags.ReuseNamespace, "reusenamespace", false, "Whether to re-use namespace for a test if it already exists.")
flag.Parse()

// If no channel is passed through the flag, initialize it as the DefaultChannel.
Expand Down
1 change: 1 addition & 0 deletions test/flags/eventing_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ type EventingEnvironmentFlags struct {
ReadyFile string
BrokerName string
BrokerNamespace string
ReuseNamespace bool
}
151 changes: 108 additions & 43 deletions test/lib/test_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ package lib

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"testing"
"time"

corev1 "k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/storage/names"

pkgTest "knative.dev/pkg/test"
"knative.dev/pkg/test/helpers"
"knative.dev/pkg/test/prow"

"knative.dev/eventing/pkg/utils"
Expand All @@ -47,6 +47,15 @@ import (
const (
podLogsDir = "pod-logs"
testPullSecretName = "kn-eventing-test-pull-secret"
MaxNamespaceSkip = 20
MaxRetries = 5
RetrySleepDuration = 2 * time.Second
)

var (
nsMutex sync.Mutex
namespaceCount int
ReuseNamespace bool
)

// ComponentsTestRunner is used to run tests against different eventing components.
Expand Down Expand Up @@ -148,19 +157,16 @@ var SetupClientOptionNoop SetupClientOption = func(*Client) {
// Setup creates the client objects needed in the e2e tests,
// and does other setups, like creating namespaces, set the test case to run in parallel, etc.
func Setup(t *testing.T, runInParallel bool, options ...SetupClientOption) *Client {
// Create a new namespace to run this test case.
namespace := makeK8sNamespace(t.Name())
t.Logf("namespace is : %q", namespace)
client, err := NewClient(
pkgTest.Flags.Kubeconfig,
pkgTest.Flags.Cluster,
namespace,
t)
client, err := CreateNamespacedClient(t)
if err != nil {
t.Fatal("Couldn't initialize clients:", err)
}

CreateNamespaceIfNeeded(t, client, namespace)
// If namespaces are re-used the pull-secret is supposed to be created in advance.
if !ReuseNamespace {
SetupServiceAccount(t, client)
SetupPullSecret(t, client)
}

// Run the test case in parallel if needed.
if runInParallel {
Expand All @@ -178,9 +184,73 @@ func Setup(t *testing.T, runInParallel bool, options ...SetupClientOption) *Clie
return client
}

func makeK8sNamespace(baseFuncName string) string {
base := helpers.MakeK8sNamePrefix(baseFuncName)
return names.SimpleNameGenerator.GenerateName(base + "-")
func CreateNamespacedClient(t *testing.T) (*Client, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: we intend to delete this lib and have it be replaced with knative-sandbox/reconciler-test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. OK. But the previous attempt that I abandoned because of this new framework coming was 11 months ago (#3313). If I understand it correctly, the majority of tests are still using this old style so it would be good to have some solution until we actually migrate everything to the new framework.

ns := ""
// Try next MaxNamespaceSkip namespaces before giving up. This should address the issue with
// development cycles when namespaces from previous runs were not cleaned properly.
for i := 0; i < MaxNamespaceSkip; i++ {
ns = NextNamespace()
client, err := NewClient(
pkgTest.Flags.Kubeconfig,
pkgTest.Flags.Cluster,
ns,
t)
if err != nil {
return nil, err
}
if ReuseNamespace {
// Re-using existing namespace, no need to create it.
// The namespace is supposed to be created in advance.
return client, nil
} else {
// The test is supposed to create a new test namespace for itself.
// Keep trying until we find a namespace that doesn't exist yet.
if err := CreateNamespaceWithRetry(client, ns); err != nil {
if apierrs.IsAlreadyExists(err) {
continue
}
return nil, err
}
}
return client, nil
}
return nil, errors.New("unable to find available namespace")
}

// NextNamespace returns the next unique namespace.
func NextNamespace() string {
ns := os.Getenv("EVENTING_E2E_NAMESPACE")
if ns == "" {
ns = "eventing-e2e"
}
return fmt.Sprintf("%s%d", ns, GetNextNamespaceId())
}

// GetNextNamespaceId return the next unique ID for the next namespace.
func GetNextNamespaceId() int {
nsMutex.Lock()
defer nsMutex.Unlock()
current := namespaceCount
namespaceCount++
return current
}

// CreateNamespaceWithRetry creates the given namespace with retries.
func CreateNamespaceWithRetry(client *Client, namespace string) error {
var (
retries int
err error
)
for retries < MaxRetries {
nsSpec := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
if _, err = client.Kube.CoreV1().Namespaces().
Create(context.Background(), nsSpec, metav1.CreateOptions{}); err == nil {
return nil
}
retries++
time.Sleep(RetrySleepDuration)
}
return err
}

// TearDown will delete created names using clients.
Expand Down Expand Up @@ -227,8 +297,11 @@ func TearDown(client *Client) {
}

client.Tracker.Clean(true)
if err := DeleteNameSpace(client); err != nil {
client.T.Logf("Could not delete the namespace %q: %v", client.Namespace, err)
// If we're reusing existing namespaces leave the deletion to the creator.
if !ReuseNamespace {
if err := DeleteNameSpace(client); err != nil {
client.T.Logf("Could not delete the namespace %q: %v", client.Namespace, err)
}
}
}

Expand All @@ -253,33 +326,25 @@ func formatEvent(e *corev1.Event) string {
}, "\n")
}

// CreateNamespaceIfNeeded creates a new namespace if it does not exist.
func CreateNamespaceIfNeeded(t *testing.T, client *Client, namespace string) {
_, err := client.Kube.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{})

if err != nil && apierrs.IsNotFound(err) {
nsSpec := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
_, err = client.Kube.CoreV1().Namespaces().Create(context.Background(), nsSpec, metav1.CreateOptions{})

if err != nil {
t.Fatalf("Failed to create Namespace: %s; %v", namespace, err)
}

// https://github.com/kubernetes/kubernetes/issues/66689
// We can only start creating pods after the default ServiceAccount is created by the kube-controller-manager.
err = waitForServiceAccountExists(client, "default", namespace)
if err != nil {
t.Fatal("The default ServiceAccount was not created for the Namespace:", namespace)
}
// SetupServiceAccount creates a new namespace if it does not exist.
func SetupServiceAccount(t *testing.T, client *Client) {
// https://github.com/kubernetes/kubernetes/issues/66689
// We can only start creating pods after the default ServiceAccount is created by the kube-controller-manager.
err := waitForServiceAccountExists(client, "default", client.Namespace)
if err != nil {
t.Fatal("The default ServiceAccount was not created for the Namespace:", client.Namespace)
}
}

// If the "default" Namespace has a secret called
// "kn-eventing-test-pull-secret" then use that as the ImagePullSecret
// on the "default" ServiceAccount in this new Namespace.
// This is needed for cases where the images are in a private registry.
_, err := utils.CopySecret(client.Kube.CoreV1(), "default", testPullSecretName, namespace, "default")
if err != nil && !apierrs.IsNotFound(err) {
t.Fatalf("error copying the secret into ns %q: %s", namespace, err)
}
// SetupPullSecret sets up kn-eventing-test-pull-secret on the client namespace.
func SetupPullSecret(t *testing.T, client *Client) {
// If the "default" Namespace has a secret called
// "kn-eventing-test-pull-secret" then use that as the ImagePullSecret
// on the "default" ServiceAccount in this new Namespace.
// This is needed for cases where the images are in a private registry.
_, err := utils.CopySecret(client.Kube.CoreV1(), "default", testPullSecretName, client.Namespace, "default")
if err != nil && !apierrs.IsNotFound(err) {
t.Fatalf("error copying the secret into ns %q: %s", client.Namespace, err)
}
}

Expand Down