func logNamespace(c clientset.Interface, namespace string) {
	ns, err := c.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{})
	if err != nil {
		if apierrs.IsNotFound(err) {
			e2elog.Logf("namespace: %v no longer exists", namespace)
			return
		}
		e2elog.Logf("namespace: %v, unable to get namespace due to error: %v", namespace, err)
		return
	}
	e2elog.Logf("namespace: %v, DeletionTimetamp: %v, Finalizers: %v, Phase: %v", ns.Name, ns.DeletionTimestamp, ns.Spec.Finalizers, ns.Status.Phase)
}
logNamespace logs detail about a namespace
package framework
import (
	"context"
	"fmt"
	"strings"
	"time"
	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	v1 "k8s.io/api/core/v1"
	crdclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/dynamic"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	vcclient "sigs.k8s.io/cluster-api-provider-nested/virtualcluster/pkg/client/clientset/versioned"
	e2elog "sigs.k8s.io/cluster-api-provider-nested/virtualcluster/test/e2e/framework/log"
	e2epod "sigs.k8s.io/cluster-api-provider-nested/virtualcluster/test/e2e/framework/pod"
	e2evc "sigs.k8s.io/cluster-api-provider-nested/virtualcluster/test/e2e/framework/virtualcluster"
)
const (
	DefaultNamespaceDeletionTimeout = 10 * time.Minute
)
var Logf = e2elog.Logf
type Framework struct {
	BaseName string
	UniqueName string
	ClientSet   clientset.Interface
	VCClientSet vcclient.Interface
	DynamicClient dynamic.Interface
	Namespace                *v1.Namespace    
	namespacesToDelete       []*v1.Namespace  
	NamespaceDeletionTimeout time.Duration
	cleanupHandle CleanupActionHandle
	Options Options
}
type Options struct {
	ClientQPS    float32
	ClientBurst  int
	GroupVersion *schema.GroupVersion
}
func NewDefaultFramework(baseName string) *Framework {
	options := Options{
		ClientQPS:   20,
		ClientBurst: 50,
	}
	return NewFramework(baseName, options, nil)
}
func NewFramework(baseName string, options Options, client clientset.Interface) *Framework {
	f := &Framework{
		BaseName:  baseName,
		Options:   options,
		ClientSet: client,
	}
	ginkgo.BeforeEach(f.BeforeEach)
	ginkgo.AfterEach(f.AfterEach)
	return f
}
func (f *Framework) BeforeEach() {
	f.cleanupHandle = AddCleanupAction(f.AfterEach)
	if f.ClientSet == nil {
		ginkgo.By("Creating a kubernetes client")
		config, err := LoadConfig()
		ExpectNoError(err)
		testDesc := ginkgo.CurrentGinkgoTestDescription()
		if len(testDesc.ComponentTexts) > 0 {
			componentTexts := strings.Join(testDesc.ComponentTexts, " ")
			config.UserAgent = fmt.Sprintf(
				"%v -- %v",
				rest.DefaultKubernetesUserAgent(),
				componentTexts)
		}
		config.QPS = f.Options.ClientQPS
		config.Burst = f.Options.ClientBurst
		if f.Options.GroupVersion != nil {
			config.GroupVersion = f.Options.GroupVersion
		}
		f.VCClientSet, err = vcclient.NewForConfig(config)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
		f.ClientSet, err = clientset.NewForConfig(config)
		ExpectNoError(err)
		f.DynamicClient, err = dynamic.NewForConfig(config)
		ExpectNoError(err)
		crdClient, err := crdclient.NewForConfig(config)
		ExpectNoError(err)
		_, err = crdClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(context.TODO(), "clusterversions.tenancy.x-k8s.io", metav1.GetOptions{})
		ExpectNoError(err, "clusterversions crd not installed")
		_, err = crdClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(context.TODO(), "virtualclusters.tenancy.x-k8s.io", metav1.GetOptions{})
		ExpectNoError(err, "virtualclusters crd not installed")
	}
	ginkgo.By(fmt.Sprintf("Building a namespace api object, basename %s", f.BaseName))
	namespace, err := f.CreateNamespace(f.BaseName, map[string]string{
		"e2e-framework": f.BaseName,
	})
	ExpectNoError(err)
	f.Namespace = namespace
	f.UniqueName = f.Namespace.GetName()
}
func (f *Framework) AfterEach() {
	RemoveCleanupAction(f.cleanupHandle)
	defer func() {
		nsDeletionErrors := map[string]error{}
		if TestContext.DeleteNamespace && (TestContext.DeleteNamespaceOnFailure || !ginkgo.CurrentGinkgoTestDescription().Failed) {
			for _, ns := range f.namespacesToDelete {
				ginkgo.By(fmt.Sprintf("Destroying namespace %q for this suite.", ns.Name))
				timeout := DefaultNamespaceDeletionTimeout
				if f.NamespaceDeletionTimeout != 0 {
					timeout = f.NamespaceDeletionTimeout
				}
				if err := deleteNS(f.ClientSet, f.DynamicClient, ns.Name, timeout); err != nil {
					if !apierrs.IsNotFound(err) {
						nsDeletionErrors[ns.Name] = err
					} else {
						e2elog.Logf("Namespace %v was already deleted", ns.Name)
					}
				}
			}
		} else {
			if !TestContext.DeleteNamespace {
				e2elog.Logf("Found DeleteNamespace=false, skipping namespace deletion!")
			} else {
				e2elog.Logf("Found DeleteNamespaceOnFailure=false and current test failed, skipping namespace deletion!")
			}
		}
		f.Namespace = nil
		f.ClientSet = nil
		f.namespacesToDelete = nil
		if len(nsDeletionErrors) != 0 {
			messages := []string{}
			for namespaceKey, namespaceErr := range nsDeletionErrors {
				messages = append(messages, fmt.Sprintf("Couldn't delete ns: %q: %s (%#v)", namespaceKey, namespaceErr, namespaceErr))
			}
			e2elog.Failf(strings.Join(messages, ","))
		}
	}()
	if ginkgo.CurrentGinkgoTestDescription().Failed && TestContext.DumpLogsOnFailure {
		DumpAllNamespaceInfo(f.ClientSet, f.Namespace.Name)
	}
}
func deleteNS(c clientset.Interface, dynamicClient dynamic.Interface, namespace string, timeout time.Duration) error {
	startTime := time.Now()
	if err := c.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{}); err != nil {
		return err
	}
	err := wait.PollImmediate(2*time.Second, timeout, func() (bool, error) {
		if _, err := c.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); err != nil {
			if apierrs.IsNotFound(err) {
				return true, nil
			}
			e2elog.Logf("Error while waiting for namespace to be terminated: %v", err)
			return false, nil
		}
		return false, nil
	})
	remainingContent, cerr := hasRemainingContent(c, dynamicClient, namespace)
	if cerr != nil {
		return cerr
	}
	remainingPods := 0
	missingTimestamp := 0
	if remainingContent {
		logNamespace(c, namespace)
		logNamespaces(c, namespace)
		remainingPods, missingTimestamp, _ = countRemainingPods(c, namespace)
	}
	if err != nil {
		if remainingContent {
			if remainingPods > 0 {
				if missingTimestamp != 0 {
					return fmt.Errorf("namespace %v was not deleted with limit: %v, pods remaining: %v, pods missing deletion timestamp: %v", namespace, err, remainingPods, missingTimestamp)
				}
				return fmt.Errorf("namespace %v was not deleted with limit: %v, pods remaining: %v", namespace, err, remainingPods)
			}
			return fmt.Errorf("namespace %v was not deleted with limit: %v, namespaced content other than pods remain", namespace, err)
		}
		return fmt.Errorf("namespace %v was not deleted with limit: %v, namespace is empty but is not yet removed", namespace, err)
	}
	e2elog.Logf("namespace %v deletion completed in %s", namespace, time.Since(startTime))
	return nil
}
func countRemainingPods(c clientset.Interface, namespace string) (int, int, error) {
	pods, err := c.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		return 0, 0, err
	}
	if len(pods.Items) == 0 {
		return 0, 0, nil
	}
	e2epod.LogPodStates(pods.Items)
	numPods := len(pods.Items)
	missingTimestamp := 0
	for _, pod := range pods.Items {
		if pod.DeletionTimestamp == nil {
			missingTimestamp++
		}
	}
	return numPods, missingTimestamp, nil
}
func logNamespaces(c clientset.Interface, namespace string) {
	namespaceList, err := c.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		e2elog.Logf("namespace: %v, unable to list namespaces: %v", namespace, err)
		return
	}
	numActive := 0
	numTerminating := 0
	for _, namespace := range namespaceList.Items {
		if namespace.Status.Phase == v1.NamespaceActive {
			numActive++
		} else {
			numTerminating++
		}
	}
	e2elog.Logf("namespace: %v, total namespaces: %v, active: %v, terminating: %v", namespace, len(namespaceList.Items), numActive, numTerminating)
}