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

🏃[e2e]: add namespace utils to the test framework #2891

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions test/framework/management_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ package framework
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/controller-runtime/pkg/client"

"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -158,3 +167,112 @@ func WaitForDeploymentsAvailable(ctx context.Context, input WaitForDeploymentsAv

}, intervals...).Should(BeTrue(), "Deployment %s/%s failed to get status.Available = True condition", input.Deployment.GetNamespace(), input.Deployment.GetName())
}

// CreateNamespaceInput is the input type for CreateNamespace.
type CreateNamespaceInput struct {
Creator Creator
Name string
Copy link
Member

Choose a reason for hiding this comment

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

Any particular reason to require a Name on input for CreateNamespace?

Could you make Name optional and default to "test-" + util.RandomString(6) if not provided? Otherwise downstream providers consuming this would still have to implement their own randomization.

Copy link
Member

@neolit123 neolit123 Apr 10, 2020

Choose a reason for hiding this comment

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

informatively, the k/k framework:

  • (in the default Before function)
  • takes the test name as input
  • adds a UUID after the test name
  • loops to make sure a namespace name+uuid does not exist
  • creates the namespace
  • (in the default After function)
  • deletes the namespace

Copy link
Member Author

Choose a reason for hiding this comment

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

@detiber Done!
@neolit123 yes, we are following a similar path

}

// CreateNamespace is used to create a namespace object.
// If name is empty, a "test-" + util.RandomString(6) name will be generated.
func CreateNamespace(ctx context.Context, input CreateNamespaceInput, intervals ...interface{}) *corev1.Namespace {
Expect(ctx).NotTo(BeNil(), "ctx is required for DeleteNamespace")
Expect(input.Creator).NotTo(BeNil(), "input.Creator is required for CreateNamespace")
if input.Name == "" {
input.Name = fmt.Sprintf("test-%s", util.RandomString(6))
}

ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: input.Name,
},
}
By(fmt.Sprintf("Creating namespace %s", input.Name))
fabriziopandini marked this conversation as resolved.
Show resolved Hide resolved
Eventually(func() error {
return input.Creator.Create(context.TODO(), ns)
}, intervals...).Should(Succeed())

return ns
}

// DeleteNamespaceInput is the input type for DeleteNamespace.
type DeleteNamespaceInput struct {
Deleter Deleter
Name string
}

// DeleteNamespace is used to delete namespace object.
func DeleteNamespace(ctx context.Context, input DeleteNamespaceInput, intervals ...interface{}) {
Expect(ctx).NotTo(BeNil(), "ctx is required for DeleteNamespace")
Expect(input.Deleter).NotTo(BeNil(), "input.Deleter is required for DeleteNamespace")
Expect(input.Name).NotTo(BeEmpty(), "input.Name is required for DeleteNamespace")
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: input.Name,
},
}
By(fmt.Sprintf("Deleting namespace %s", input.Name))
fabriziopandini marked this conversation as resolved.
Show resolved Hide resolved
Eventually(func() error {
return input.Deleter.Delete(context.TODO(), ns)
}, intervals...).Should(Succeed())
}

// WatchNamespaceEventsInput is the input type for WatchNamespaceEvents.
type WatchNamespaceEventsInput struct {
ClientSet *kubernetes.Clientset
Name string
LogPath string
}

// WatchNamespaceEvents creates a watcher that streams namespace events into a file.
// Example usage:
// ctx, cancelWatches := context.WithCancel(context.Background())
// go func() {
// defer GinkgoRecover()
// framework.WatchNamespaceEvents(ctx, framework.WatchNamespaceEventsInput{
// ClientSet: clientSet,
// Name: namespace.Name,
// LogPath: logPath,
// })
// }()
// defer cancelWatches()
func WatchNamespaceEvents(ctx context.Context, input WatchNamespaceEventsInput) {
Expect(ctx).NotTo(BeNil(), "ctx is required for WatchNamespaceEvents")
Expect(input.ClientSet).NotTo(BeNil(), "input.ClientSet is required for WatchNamespaceEvents")
Expect(input.Name).NotTo(BeEmpty(), "input.Name is required for WatchNamespaceEvents")

logFile := path.Join(input.LogPath, "resources", input.Name, "events.log")
fmt.Fprintf(GinkgoWriter, "Creating directory: %s\n", filepath.Dir(logFile))
Expect(os.MkdirAll(filepath.Dir(logFile), 0755)).To(Succeed())

f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
Expect(err).NotTo(HaveOccurred())
defer f.Close()

informerFactory := informers.NewSharedInformerFactoryWithOptions(
input.ClientSet,
10*time.Minute,
informers.WithNamespace(input.Name),
)
eventInformer := informerFactory.Core().V1().Events().Informer()
eventInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
e := obj.(*corev1.Event)
f.WriteString(fmt.Sprintf("[New Event] %s/%s\n\tresource: %s/%s/%s\n\treason: %s\n\tmessage: %s\n\tfull: %#v\n",
e.Namespace, e.Name, e.InvolvedObject.APIVersion, e.InvolvedObject.Kind, e.InvolvedObject.Name, e.Reason, e.Message, e))
},
UpdateFunc: func(_, obj interface{}) {
e := obj.(*corev1.Event)
f.WriteString(fmt.Sprintf("[Updated Event] %s/%s\n\tresource: %s/%s/%s\n\treason: %s\n\tmessage: %s\n\tfull: %#v\n",
e.Namespace, e.Name, e.InvolvedObject.APIVersion, e.InvolvedObject.Kind, e.InvolvedObject.Name, e.Reason, e.Message, e))
},
DeleteFunc: func(obj interface{}) {},
})

stopInformer := make(chan struct{})
defer close(stopInformer)
informerFactory.Start(stopInformer)
<-ctx.Done()
stopInformer <- struct{}{}
}