@@ -36,6 +36,7 @@ import (
36
36
"k8s.io/kubectl/pkg/util/podutils"
37
37
38
38
"github.com/onsi/ginkgo/v2"
39
+ ginkgotypes "github.com/onsi/ginkgo/v2/types"
39
40
"github.com/onsi/gomega"
40
41
41
42
"k8s.io/kubernetes/test/e2e/framework"
@@ -56,6 +57,19 @@ const (
56
57
57
58
// it is copied from k8s.io/kubernetes/pkg/kubelet/sysctl
58
59
forbiddenReason = "SysctlForbidden"
60
+
61
+ // which test created this pod?
62
+ AnnotationTestOwner = "owner.test"
63
+ )
64
+
65
+ // global flags so we can enable features per-suite instead of per-client.
66
+ var (
67
+ // GlobalOwnerTracking controls if newly created PodClients should automatically annotate
68
+ // the pod with the owner test. The owner test is identified by "sourcecodepath:linenumber".
69
+ // Annotating the pods this way is useful to troubleshoot tests which do insufficient cleanup.
70
+ // Default is false to maximize backward compatibility.
71
+ // See also: WithOwnerTracking, AnnotationTestOwner
72
+ GlobalOwnerTracking bool
59
73
)
60
74
61
75
// ImagePrePullList is the images used in the current test suite. It should be initialized in test suite and
@@ -68,9 +82,10 @@ var ImagePrePullList sets.String
68
82
// node e2e pod scheduling.
69
83
func NewPodClient (f * framework.Framework ) * PodClient {
70
84
return & PodClient {
71
- f : f ,
72
- PodInterface : f .ClientSet .CoreV1 ().Pods (f .Namespace .Name ),
73
- namespace : f .Namespace .Name ,
85
+ f : f ,
86
+ PodInterface : f .ClientSet .CoreV1 ().Pods (f .Namespace .Name ),
87
+ namespace : f .Namespace .Name ,
88
+ ownerTracking : GlobalOwnerTracking ,
74
89
}
75
90
}
76
91
@@ -79,29 +94,45 @@ func NewPodClient(f *framework.Framework) *PodClient {
79
94
// node e2e pod scheduling.
80
95
func PodClientNS (f * framework.Framework , namespace string ) * PodClient {
81
96
return & PodClient {
82
- f : f ,
83
- PodInterface : f .ClientSet .CoreV1 ().Pods (namespace ),
84
- namespace : namespace ,
97
+ f : f ,
98
+ PodInterface : f .ClientSet .CoreV1 ().Pods (namespace ),
99
+ namespace : namespace ,
100
+ ownerTracking : GlobalOwnerTracking ,
85
101
}
86
102
}
87
103
88
104
// PodClient is a struct for pod client.
89
105
type PodClient struct {
90
106
f * framework.Framework
91
107
v1core.PodInterface
92
- namespace string
108
+ namespace string
109
+ ownerTracking bool
110
+ }
111
+
112
+ // WithOwnerTracking controls automatic add of annotations recording the code location
113
+ // which created a pod. This is helpful when troubleshooting e2e tests (like e2e_node)
114
+ // which leak pods because insufficient cleanup.
115
+ // Note we want a shallow clone to avoid mutating the receiver.
116
+ // The default is the value of GlobalOwnerTracking *when the client was created*.
117
+ func (c PodClient ) WithOwnerTracking (value bool ) * PodClient {
118
+ c .ownerTracking = value
119
+ return & c
93
120
}
94
121
95
122
// Create creates a new pod according to the framework specifications (don't wait for it to start).
96
123
func (c * PodClient ) Create (ctx context.Context , pod * v1.Pod ) * v1.Pod {
124
+ ginkgo .GinkgoHelper ()
97
125
c .mungeSpec (pod )
126
+ c .setOwnerAnnotation (pod )
98
127
p , err := c .PodInterface .Create (ctx , pod , metav1.CreateOptions {})
99
128
framework .ExpectNoError (err , "Error creating Pod" )
100
129
return p
130
+
101
131
}
102
132
103
133
// CreateSync creates a new pod according to the framework specifications, and wait for it to start and be running and ready.
104
134
func (c * PodClient ) CreateSync (ctx context.Context , pod * v1.Pod ) * v1.Pod {
135
+ ginkgo .GinkgoHelper ()
105
136
p := c .Create (ctx , pod )
106
137
framework .ExpectNoError (WaitTimeoutForPodReadyInNamespace (ctx , c .f .ClientSet , p .Name , c .namespace , framework .PodStartTimeout ))
107
138
// Get the newest pod after it becomes running and ready, some status may change after pod created, such as pod ip.
@@ -112,6 +143,7 @@ func (c *PodClient) CreateSync(ctx context.Context, pod *v1.Pod) *v1.Pod {
112
143
113
144
// CreateBatch create a batch of pods. All pods are created before waiting.
114
145
func (c * PodClient ) CreateBatch (ctx context.Context , pods []* v1.Pod ) []* v1.Pod {
146
+ ginkgo .GinkgoHelper ()
115
147
ps := make ([]* v1.Pod , len (pods ))
116
148
var wg sync.WaitGroup
117
149
for i , pod := range pods {
@@ -192,6 +224,19 @@ func (c *PodClient) DeleteSync(ctx context.Context, name string, options metav1.
192
224
framework .ExpectNoError (WaitForPodNotFoundInNamespace (ctx , c .f .ClientSet , name , c .namespace , timeout ), "wait for pod %q to disappear" , name )
193
225
}
194
226
227
+ // addTestOrigin adds annotations to help identifying tests which incorrectly leak pods because insufficient cleanup
228
+ func (c * PodClient ) setOwnerAnnotation (pod * v1.Pod ) {
229
+ if ! c .ownerTracking {
230
+ return
231
+ }
232
+ ginkgo .GinkgoHelper ()
233
+ location := ginkgotypes .NewCodeLocation (0 )
234
+ if pod .Annotations == nil {
235
+ pod .Annotations = make (map [string ]string )
236
+ }
237
+ pod .Annotations [AnnotationTestOwner ] = fmt .Sprintf ("%s:%d" , location .FileName , location .LineNumber )
238
+ }
239
+
195
240
// mungeSpec apply test-suite specific transformations to the pod spec.
196
241
func (c * PodClient ) mungeSpec (pod * v1.Pod ) {
197
242
if ! framework .TestContext .NodeE2E {
0 commit comments