Fix duplicate namespace deletion errors, improve namespace deletion error output
This commit is contained in:
@@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/fields"
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
@@ -62,6 +63,7 @@ type Framework struct {
|
|||||||
Clientset_1_2 *release_1_2.Clientset
|
Clientset_1_2 *release_1_2.Clientset
|
||||||
Clientset_1_3 *release_1_3.Clientset
|
Clientset_1_3 *release_1_3.Clientset
|
||||||
StagingClient *release_1_4.Clientset
|
StagingClient *release_1_4.Clientset
|
||||||
|
ClientPool dynamic.ClientPool
|
||||||
|
|
||||||
Namespace *api.Namespace // Every test has at least one namespace
|
Namespace *api.Namespace // Every test has at least one namespace
|
||||||
namespacesToDelete []*api.Namespace // Some tests have more than one.
|
namespacesToDelete []*api.Namespace // Some tests have more than one.
|
||||||
@@ -192,6 +194,7 @@ func (f *Framework) BeforeEach() {
|
|||||||
clientRepoConfig := getClientRepoConfig(config)
|
clientRepoConfig := getClientRepoConfig(config)
|
||||||
f.StagingClient, err = release_1_4.NewForConfig(clientRepoConfig)
|
f.StagingClient, err = release_1_4.NewForConfig(clientRepoConfig)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
f.ClientPool = dynamic.NewClientPool(config, dynamic.LegacyAPIPathResolverFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.federated {
|
if f.federated {
|
||||||
@@ -295,30 +298,27 @@ func (f *Framework) AfterEach() {
|
|||||||
// DeleteNamespace at the very end in defer, to avoid any
|
// DeleteNamespace at the very end in defer, to avoid any
|
||||||
// expectation failures preventing deleting the namespace.
|
// expectation failures preventing deleting the namespace.
|
||||||
defer func() {
|
defer func() {
|
||||||
|
nsDeletionErrors := map[string]error{}
|
||||||
if TestContext.DeleteNamespace {
|
if TestContext.DeleteNamespace {
|
||||||
for _, ns := range f.namespacesToDelete {
|
for _, ns := range f.namespacesToDelete {
|
||||||
By(fmt.Sprintf("Destroying namespace %q for this suite.", ns.Name))
|
By(fmt.Sprintf("Destroying namespace %q for this suite.", ns.Name))
|
||||||
|
|
||||||
timeout := 5 * time.Minute
|
timeout := 5 * time.Minute
|
||||||
if f.NamespaceDeletionTimeout != 0 {
|
if f.NamespaceDeletionTimeout != 0 {
|
||||||
timeout = f.NamespaceDeletionTimeout
|
timeout = f.NamespaceDeletionTimeout
|
||||||
}
|
}
|
||||||
if err := deleteNS(f.Client, ns.Name, timeout); err != nil {
|
if err := deleteNS(f.Client, f.ClientPool, ns.Name, timeout); err != nil {
|
||||||
if !apierrs.IsNotFound(err) {
|
if !apierrs.IsNotFound(err) {
|
||||||
Failf("Couldn't delete ns %q: %s", ns.Name, err)
|
nsDeletionErrors[ns.Name] = err
|
||||||
} else {
|
} else {
|
||||||
Logf("Namespace %v was already deleted", ns.Name)
|
Logf("Namespace %v was already deleted", ns.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.namespacesToDelete = nil
|
|
||||||
|
|
||||||
// Delete the federation namespace.
|
// Delete the federation namespace.
|
||||||
// TODO(nikhiljindal): Uncomment this, once https://github.com/kubernetes/kubernetes/issues/31077 is fixed.
|
// TODO(nikhiljindal): Uncomment this, once https://github.com/kubernetes/kubernetes/issues/31077 is fixed.
|
||||||
// In the meantime, we will have these extra namespaces in all clusters.
|
// In the meantime, we will have these extra namespaces in all clusters.
|
||||||
// Note: this will not cause any failure since we create a new namespace for each test in BeforeEach().
|
// Note: this will not cause any failure since we create a new namespace for each test in BeforeEach().
|
||||||
// f.deleteFederationNs()
|
// f.deleteFederationNs()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Logf("Found DeleteNamespace=false, skipping namespace deletion!")
|
Logf("Found DeleteNamespace=false, skipping namespace deletion!")
|
||||||
}
|
}
|
||||||
@@ -327,6 +327,16 @@ func (f *Framework) AfterEach() {
|
|||||||
f.Namespace = nil
|
f.Namespace = nil
|
||||||
f.FederationNamespace = nil
|
f.FederationNamespace = nil
|
||||||
f.Client = nil
|
f.Client = nil
|
||||||
|
f.namespacesToDelete = nil
|
||||||
|
|
||||||
|
// if we had errors deleting, report them now.
|
||||||
|
if len(nsDeletionErrors) != 0 {
|
||||||
|
messages := []string{}
|
||||||
|
for namespaceKey, namespaceErr := range nsDeletionErrors {
|
||||||
|
messages = append(messages, fmt.Sprintf("Couldn't delete ns: %q: %s", namespaceKey, namespaceErr))
|
||||||
|
}
|
||||||
|
Failf(strings.Join(messages, ","))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if f.federated {
|
if f.federated {
|
||||||
|
|||||||
@@ -44,11 +44,13 @@ import (
|
|||||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/resource"
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/client/cache"
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||||
@@ -1033,11 +1035,12 @@ func CheckTestingNSDeletedExcept(c *client.Client, skip string) error {
|
|||||||
|
|
||||||
// deleteNS deletes the provided namespace, waits for it to be completely deleted, and then checks
|
// deleteNS deletes the provided namespace, waits for it to be completely deleted, and then checks
|
||||||
// whether there are any pods remaining in a non-terminating state.
|
// whether there are any pods remaining in a non-terminating state.
|
||||||
func deleteNS(c *client.Client, namespace string, timeout time.Duration) error {
|
func deleteNS(c *client.Client, clientPool dynamic.ClientPool, namespace string, timeout time.Duration) error {
|
||||||
if err := c.Namespaces().Delete(namespace); err != nil {
|
if err := c.Namespaces().Delete(namespace); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for namespace to delete or timeout.
|
||||||
err := wait.PollImmediate(5*time.Second, timeout, func() (bool, error) {
|
err := wait.PollImmediate(5*time.Second, timeout, func() (bool, error) {
|
||||||
if _, err := c.Namespaces().Get(namespace); err != nil {
|
if _, err := c.Namespaces().Get(namespace); err != nil {
|
||||||
if apierrs.IsNotFound(err) {
|
if apierrs.IsNotFound(err) {
|
||||||
@@ -1049,40 +1052,155 @@ func deleteNS(c *client.Client, namespace string, timeout time.Duration) error {
|
|||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// check for pods that were not deleted
|
// verify there is no more remaining content in the namespace
|
||||||
remaining := []string{}
|
remainingContent, cerr := hasRemainingContent(c, clientPool, namespace)
|
||||||
remainingPods := []api.Pod{}
|
if cerr != nil {
|
||||||
missingTimestamp := false
|
return cerr
|
||||||
if pods, perr := c.Pods(namespace).List(api.ListOptions{}); perr == nil {
|
|
||||||
for _, pod := range pods.Items {
|
|
||||||
Logf("Pod %s %s on node %s remains, has deletion timestamp %s", namespace, pod.Name, pod.Spec.NodeName, pod.DeletionTimestamp)
|
|
||||||
remaining = append(remaining, fmt.Sprintf("%s{Reason=%s}", pod.Name, pod.Status.Reason))
|
|
||||||
remainingPods = append(remainingPods, pod)
|
|
||||||
if pod.DeletionTimestamp == nil {
|
|
||||||
missingTimestamp = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// log pod status
|
// if content remains, let's dump information about the namespace, and system for flake debugging.
|
||||||
if len(remainingPods) > 0 {
|
remainingPods := 0
|
||||||
logPodStates(remainingPods)
|
missingTimestamp := 0
|
||||||
|
if remainingContent {
|
||||||
|
// log information about namespace, and set of namespaces in api server to help flake detection
|
||||||
|
logNamespace(c, namespace)
|
||||||
|
logNamespaces(c, namespace)
|
||||||
|
|
||||||
|
// if we can, check if there were pods remaining with no timestamp.
|
||||||
|
remainingPods, missingTimestamp, _ = countRemainingPods(c, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// a timeout occurred
|
// a timeout waiting for namespace deletion happened!
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if missingTimestamp {
|
// some content remains in the namespace
|
||||||
return fmt.Errorf("namespace %s was not deleted within limit: %v, some pods were not marked with a deletion timestamp, pods remaining: %v", namespace, err, remaining)
|
if remainingContent {
|
||||||
|
// pods remain
|
||||||
|
if remainingPods > 0 {
|
||||||
|
// but they were all undergoing deletion (kubelet is probably culprit)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// pods remained, but were not undergoing deletion (namespace controller is probably culprit)
|
||||||
|
return fmt.Errorf("namespace %v was not deleted with limit: %v, pods remaining: %v", namespace, err, remainingPods)
|
||||||
|
}
|
||||||
|
// other content remains (namespace controller is probably screwed up)
|
||||||
|
return fmt.Errorf("namespace %v was not deleted with limit: %v, namespaced content other than pods remain", namespace, err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("namespace %s was not deleted within limit: %v, pods remaining: %v", namespace, err, remaining)
|
// no remaining content, but namespace was not deleted (namespace controller is probably wedged)
|
||||||
}
|
return fmt.Errorf("namespace %v was not deleted with limit: %v, namespace is empty but is not yet removed", namespace, err)
|
||||||
// pods were not deleted but the namespace was deleted
|
|
||||||
if len(remaining) > 0 {
|
|
||||||
return fmt.Errorf("pods remained within namespace %s after deletion: %v", namespace, remaining)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logNamespaces logs the number of namespaces by phase
|
||||||
|
// namespace is the namespace the test was operating against that failed to delete so it can be grepped in logs
|
||||||
|
func logNamespaces(c *client.Client, namespace string) {
|
||||||
|
namespaceList, err := c.Namespaces().List(api.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
Logf("namespace: %v, unable to list namespaces: %v", namespace, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numActive := 0
|
||||||
|
numTerminating := 0
|
||||||
|
for _, namespace := range namespaceList.Items {
|
||||||
|
if namespace.Status.Phase == api.NamespaceActive {
|
||||||
|
numActive++
|
||||||
|
} else {
|
||||||
|
numTerminating++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logf("namespace: %v, total namespaces: %v, active: %v, terminating: %v", namespace, len(namespaceList.Items), numActive, numTerminating)
|
||||||
|
}
|
||||||
|
|
||||||
|
// logNamespace logs detail about a namespace
|
||||||
|
func logNamespace(c *client.Client, namespace string) {
|
||||||
|
ns, err := c.Namespaces().Get(namespace)
|
||||||
|
if err != nil {
|
||||||
|
if apierrs.IsNotFound(err) {
|
||||||
|
Logf("namespace: %v no longer exists", namespace)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Logf("namespace: %v, unable to get namespace due to error: %v", namespace, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Logf("namespace: %v, DeletionTimetamp: %v, Finalizers: %v, Phase: %v", ns.Name, ns.DeletionTimestamp, ns.Spec.Finalizers, ns.Status.Phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// countRemainingPods queries the server to count number of remaining pods, and number of pods that had a missing deletion timestamp.
|
||||||
|
func countRemainingPods(c *client.Client, namespace string) (int, int, error) {
|
||||||
|
// check for remaining pods
|
||||||
|
pods, err := c.Pods(namespace).List(api.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing remains!
|
||||||
|
if len(pods.Items) == 0 {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stuff remains, log about it
|
||||||
|
logPodStates(pods.Items)
|
||||||
|
|
||||||
|
// check if there were any pods with missing deletion timestamp
|
||||||
|
numPods := len(pods.Items)
|
||||||
|
missingTimestamp := 0
|
||||||
|
for _, pod := range pods.Items {
|
||||||
|
if pod.DeletionTimestamp == nil {
|
||||||
|
missingTimestamp++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numPods, missingTimestamp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasRemainingContent checks if there is remaining content in the namespace via API discovery
|
||||||
|
func hasRemainingContent(c *client.Client, clientPool dynamic.ClientPool, namespace string) (bool, error) {
|
||||||
|
// some tests generate their own framework.Client rather than the default
|
||||||
|
// TODO: ensure every test call has a configured clientPool
|
||||||
|
if clientPool == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// find out what content is supported on the server
|
||||||
|
groupVersionResources, err := c.Discovery().ServerPreferredNamespacedResources()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentRemaining := false
|
||||||
|
|
||||||
|
// dump how many of resource type is on the server in a log.
|
||||||
|
for _, gvr := range groupVersionResources {
|
||||||
|
// get a client for this group version...
|
||||||
|
dynamicClient, err := clientPool.ClientForGroupVersion(gvr.GroupVersion())
|
||||||
|
if err != nil {
|
||||||
|
// not all resource types support list, so some errors here are normal depending on the resource type.
|
||||||
|
Logf("namespace: %s, unable to get client - gvr: %v, error: %v", namespace, gvr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// get the api resource
|
||||||
|
apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true}
|
||||||
|
obj, err := dynamicClient.Resource(&apiResource, namespace).List(&v1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
// not all resources support list, so we ignore those
|
||||||
|
if apierrs.IsMethodNotSupported(err) || apierrs.IsNotFound(err) || apierrs.IsForbidden(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
unstructuredList, ok := obj.(*runtime.UnstructuredList)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("namespace: %s, resource: %s, expected *runtime.UnstructuredList, got %#v", namespace, apiResource.Name, obj)
|
||||||
|
}
|
||||||
|
if len(unstructuredList.Items) > 0 {
|
||||||
|
Logf("namespace: %s, resource: %s, items remaining: %v", namespace, apiResource.Name, len(unstructuredList.Items))
|
||||||
|
contentRemaining = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentRemaining, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ContainerInitInvariant(older, newer runtime.Object) error {
|
func ContainerInitInvariant(older, newer runtime.Object) error {
|
||||||
oldPod := older.(*api.Pod)
|
oldPod := older.(*api.Pod)
|
||||||
newPod := newer.(*api.Pod)
|
newPod := newer.(*api.Pod)
|
||||||
|
|||||||
Reference in New Issue
Block a user