1
16
17 package apps
18
19 import (
20 "context"
21 "fmt"
22 "time"
23
24 "github.com/onsi/ginkgo/v2"
25 "github.com/onsi/gomega"
26
27 appsv1 "k8s.io/api/apps/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/labels"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/types"
32 utilrand "k8s.io/apimachinery/pkg/util/rand"
33 "k8s.io/apimachinery/pkg/util/wait"
34 clientset "k8s.io/client-go/kubernetes"
35 "k8s.io/client-go/kubernetes/scheme"
36 "k8s.io/client-go/util/retry"
37 extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions"
38 "k8s.io/kubernetes/pkg/controller"
39 labelsutil "k8s.io/kubernetes/pkg/util/labels"
40 "k8s.io/kubernetes/test/e2e/framework"
41 e2edaemonset "k8s.io/kubernetes/test/e2e/framework/daemonset"
42 e2eresource "k8s.io/kubernetes/test/e2e/framework/resource"
43 admissionapi "k8s.io/pod-security-admission/api"
44 "k8s.io/utils/pointer"
45 )
46
47 const (
48 controllerRevisionRetryPeriod = 1 * time.Second
49 controllerRevisionRetryTimeout = 1 * time.Minute
50 )
51
52
53
54
55
56
57 var _ = SIGDescribe("ControllerRevision", framework.WithSerial(), func() {
58 var f *framework.Framework
59
60 ginkgo.AfterEach(func(ctx context.Context) {
61
62 daemonsets, err := f.ClientSet.AppsV1().DaemonSets(f.Namespace.Name).List(ctx, metav1.ListOptions{})
63 framework.ExpectNoError(err, "unable to dump DaemonSets")
64 if daemonsets != nil && len(daemonsets.Items) > 0 {
65 for _, ds := range daemonsets.Items {
66 ginkgo.By(fmt.Sprintf("Deleting DaemonSet %q", ds.Name))
67 framework.ExpectNoError(e2eresource.DeleteResourceAndWaitForGC(ctx, f.ClientSet, extensionsinternal.Kind("DaemonSet"), f.Namespace.Name, ds.Name))
68 err = wait.PollUntilContextTimeout(ctx, dsRetryPeriod, dsRetryTimeout, true, checkRunningOnNoNodes(f, &ds))
69 framework.ExpectNoError(err, "error waiting for daemon pod to be reaped")
70 }
71 }
72 if daemonsets, err := f.ClientSet.AppsV1().DaemonSets(f.Namespace.Name).List(ctx, metav1.ListOptions{}); err == nil {
73 framework.Logf("daemonset: %s", runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...), daemonsets))
74 } else {
75 framework.Logf("unable to dump daemonsets: %v", err)
76 }
77 if pods, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(ctx, metav1.ListOptions{}); err == nil {
78 framework.Logf("pods: %s", runtime.EncodeOrDie(scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...), pods))
79 } else {
80 framework.Logf("unable to dump pods: %v", err)
81 }
82 err = clearDaemonSetNodeLabels(ctx, f.ClientSet)
83 framework.ExpectNoError(err)
84 })
85
86 f = framework.NewDefaultFramework("controllerrevisions")
87 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
88
89 image := WebserverImage
90 dsName := "e2e-" + utilrand.String(5) + "-daemon-set"
91
92 var ns string
93 var c clientset.Interface
94
95 ginkgo.BeforeEach(func(ctx context.Context) {
96 ns = f.Namespace.Name
97
98 c = f.ClientSet
99
100 updatedNS, err := patchNamespaceAnnotations(ctx, c, ns)
101 framework.ExpectNoError(err)
102
103 ns = updatedNS.Name
104
105 err = clearDaemonSetNodeLabels(ctx, c)
106 framework.ExpectNoError(err)
107 })
108
109
126 framework.ConformanceIt("should manage the lifecycle of a ControllerRevision", func(ctx context.Context) {
127 csAppsV1 := f.ClientSet.AppsV1()
128
129 dsLabel := map[string]string{"daemonset-name": dsName}
130 dsLabelSelector := labels.SelectorFromSet(dsLabel).String()
131
132 ginkgo.By(fmt.Sprintf("Creating DaemonSet %q", dsName))
133 testDaemonset, err := csAppsV1.DaemonSets(ns).Create(ctx, newDaemonSetWithLabel(dsName, image, dsLabel), metav1.CreateOptions{})
134 framework.ExpectNoError(err)
135
136 ginkgo.By("Check that daemon pods launch on every node of the cluster.")
137 err = wait.PollUntilContextTimeout(ctx, dsRetryPeriod, dsRetryTimeout, true, checkRunningOnAllNodes(f, testDaemonset))
138 framework.ExpectNoError(err, "error waiting for daemon pod to start")
139 err = e2edaemonset.CheckDaemonStatus(ctx, f, dsName)
140 framework.ExpectNoError(err)
141
142 ginkgo.By(fmt.Sprintf("Confirm DaemonSet %q successfully created with %q label", dsName, dsLabelSelector))
143 dsList, err := csAppsV1.DaemonSets("").List(ctx, metav1.ListOptions{LabelSelector: dsLabelSelector})
144 framework.ExpectNoError(err, "failed to list Daemon Sets")
145 gomega.Expect(dsList.Items).To(gomega.HaveLen(1), "filtered list wasn't found")
146
147 ds, err := c.AppsV1().DaemonSets(ns).Get(ctx, dsName, metav1.GetOptions{})
148 framework.ExpectNoError(err)
149
150
151 ginkgo.By(fmt.Sprintf("Listing all ControllerRevisions with label %q", dsLabelSelector))
152 revs, err := csAppsV1.ControllerRevisions("").List(ctx, metav1.ListOptions{LabelSelector: dsLabelSelector})
153 framework.ExpectNoError(err, "Failed to list ControllerRevision: %v", err)
154 gomega.Expect(revs.Items).To(gomega.HaveLen(1), "Failed to find any controllerRevisions")
155
156
157 var initialRevision *appsv1.ControllerRevision
158
159 rev := revs.Items[0]
160 oref := rev.OwnerReferences[0]
161 if oref.Kind == "DaemonSet" && oref.UID == ds.UID {
162 framework.Logf("Located ControllerRevision: %q", rev.Name)
163 initialRevision, err = csAppsV1.ControllerRevisions(ns).Get(ctx, rev.Name, metav1.GetOptions{})
164 framework.ExpectNoError(err, "failed to lookup ControllerRevision: %v", err)
165 gomega.Expect(initialRevision).NotTo(gomega.BeNil(), "failed to lookup ControllerRevision: %v", initialRevision)
166 }
167
168 ginkgo.By(fmt.Sprintf("Patching ControllerRevision %q", initialRevision.Name))
169 payload := "{\"metadata\":{\"labels\":{\"" + initialRevision.Name + "\":\"patched\"}}}"
170 patchedControllerRevision, err := csAppsV1.ControllerRevisions(ns).Patch(ctx, initialRevision.Name, types.StrategicMergePatchType, []byte(payload), metav1.PatchOptions{})
171 framework.ExpectNoError(err, "failed to patch ControllerRevision %s in namespace %s", initialRevision.Name, ns)
172 gomega.Expect(patchedControllerRevision.Labels).To(gomega.HaveKeyWithValue(initialRevision.Name, "patched"), "Did not find 'patched' label for this ControllerRevision. Current labels: %v", patchedControllerRevision.Labels)
173 framework.Logf("%s has been patched", patchedControllerRevision.Name)
174
175 ginkgo.By("Create a new ControllerRevision")
176 ds.Spec.Template.Spec.TerminationGracePeriodSeconds = pointer.Int64(1)
177 newHash, newName := hashAndNameForDaemonSet(ds)
178 newRevision := &appsv1.ControllerRevision{
179 ObjectMeta: metav1.ObjectMeta{
180 Name: newName,
181 Namespace: ds.Namespace,
182 Labels: labelsutil.CloneAndAddLabel(ds.Spec.Template.Labels, appsv1.DefaultDaemonSetUniqueLabelKey, newHash),
183 Annotations: ds.Annotations,
184 OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ds, appsv1.SchemeGroupVersion.WithKind("DaemonSet"))},
185 },
186 Data: initialRevision.Data,
187 Revision: initialRevision.Revision + 1,
188 }
189 newControllerRevision, err := csAppsV1.ControllerRevisions(ns).Create(ctx, newRevision, metav1.CreateOptions{})
190 framework.ExpectNoError(err, "Failed to create ControllerRevision: %v", err)
191 framework.Logf("Created ControllerRevision: %s", newControllerRevision.Name)
192
193 ginkgo.By("Confirm that there are two ControllerRevisions")
194 err = wait.PollUntilContextTimeout(ctx, controllerRevisionRetryPeriod, controllerRevisionRetryTimeout, true, checkControllerRevisionListQuantity(f, dsLabelSelector, 2))
195 framework.ExpectNoError(err, "failed to count required ControllerRevisions")
196
197 ginkgo.By(fmt.Sprintf("Deleting ControllerRevision %q", initialRevision.Name))
198 err = csAppsV1.ControllerRevisions(ns).Delete(ctx, initialRevision.Name, metav1.DeleteOptions{})
199 framework.ExpectNoError(err, "Failed to delete ControllerRevision: %v", err)
200
201 ginkgo.By("Confirm that there is only one ControllerRevision")
202 err = wait.PollUntilContextTimeout(ctx, controllerRevisionRetryPeriod, controllerRevisionRetryTimeout, true, checkControllerRevisionListQuantity(f, dsLabelSelector, 1))
203 framework.ExpectNoError(err, "failed to count required ControllerRevisions")
204
205 listControllerRevisions, err := csAppsV1.ControllerRevisions(ns).List(ctx, metav1.ListOptions{})
206 currentControllerRevision := listControllerRevisions.Items[0]
207
208 ginkgo.By(fmt.Sprintf("Updating ControllerRevision %q", currentControllerRevision.Name))
209 var updatedControllerRevision *appsv1.ControllerRevision
210
211 err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
212 updatedControllerRevision, err = csAppsV1.ControllerRevisions(ns).Get(ctx, currentControllerRevision.Name, metav1.GetOptions{})
213 framework.ExpectNoError(err, "Unable to get ControllerRevision %s", currentControllerRevision.Name)
214 updatedControllerRevision.Labels[currentControllerRevision.Name] = "updated"
215 updatedControllerRevision, err = csAppsV1.ControllerRevisions(ns).Update(ctx, updatedControllerRevision, metav1.UpdateOptions{})
216 return err
217 })
218 framework.ExpectNoError(err, "failed to update ControllerRevision in namespace: %s", ns)
219 gomega.Expect(updatedControllerRevision.Labels).To(gomega.HaveKeyWithValue(currentControllerRevision.Name, "updated"), "Did not find 'updated' label for this ControllerRevision. Current labels: %v", updatedControllerRevision.Labels)
220 framework.Logf("%s has been updated", updatedControllerRevision.Name)
221
222 ginkgo.By("Generate another ControllerRevision by patching the Daemonset")
223 patch := fmt.Sprintf(`{"spec":{"template":{"spec":{"terminationGracePeriodSeconds": %d}}},"updateStrategy":{"type":"RollingUpdate"}}`, 1)
224
225 _, err = c.AppsV1().DaemonSets(ns).Patch(ctx, dsName, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{})
226 framework.ExpectNoError(err, "error patching daemon set")
227
228 ginkgo.By("Confirm that there are two ControllerRevisions")
229 err = wait.PollUntilContextTimeout(ctx, controllerRevisionRetryPeriod, controllerRevisionRetryTimeout, true, checkControllerRevisionListQuantity(f, dsLabelSelector, 2))
230 framework.ExpectNoError(err, "failed to count required ControllerRevisions")
231
232 updatedLabel := map[string]string{updatedControllerRevision.Name: "updated"}
233 updatedLabelSelector := labels.SelectorFromSet(updatedLabel).String()
234
235 ginkgo.By(fmt.Sprintf("Removing a ControllerRevision via 'DeleteCollection' with labelSelector: %q", updatedLabelSelector))
236 err = csAppsV1.ControllerRevisions(ns).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: updatedLabelSelector})
237 framework.ExpectNoError(err, "Failed to delete ControllerRevision: %v", err)
238
239 ginkgo.By("Confirm that there is only one ControllerRevision")
240 err = wait.PollUntilContextTimeout(ctx, controllerRevisionRetryPeriod, controllerRevisionRetryTimeout, true, checkControllerRevisionListQuantity(f, dsLabelSelector, 1))
241 framework.ExpectNoError(err, "failed to count required ControllerRevisions")
242
243 list, err := csAppsV1.ControllerRevisions(ns).List(ctx, metav1.ListOptions{})
244 framework.ExpectNoError(err, "failed to list ControllerRevision")
245 gomega.Expect(list.Items[0].Revision).To(gomega.Equal(int64(3)), "failed to find the expected revision for the Controller")
246 framework.Logf("ControllerRevision %q has revision %d", list.Items[0].Name, list.Items[0].Revision)
247 })
248 })
249
250 func checkControllerRevisionListQuantity(f *framework.Framework, label string, quantity int) func(ctx context.Context) (bool, error) {
251 return func(ctx context.Context) (bool, error) {
252 var err error
253
254 framework.Logf("Requesting list of ControllerRevisions to confirm quantity")
255
256 list, err := f.ClientSet.AppsV1().ControllerRevisions(f.Namespace.Name).List(ctx, metav1.ListOptions{
257 LabelSelector: label})
258 if err != nil {
259 return false, err
260 }
261
262 if len(list.Items) != quantity {
263 return false, nil
264 }
265 framework.Logf("Found %d ControllerRevisions", quantity)
266 return true, nil
267 }
268 }
269
270 func hashAndNameForDaemonSet(ds *appsv1.DaemonSet) (string, string) {
271 hash := fmt.Sprint(controller.ComputeHash(&ds.Spec.Template, ds.Status.CollisionCount))
272 name := ds.Name + "-" + hash
273 return hash, name
274 }
275
View as plain text