1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package testcontroller
16
17 import (
18 "context"
19 "fmt"
20 "reflect"
21 "strconv"
22 "strings"
23 "testing"
24 "time"
25
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
27 testgcp "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/gcp"
28
29 v1 "k8s.io/api/core/v1"
30 "k8s.io/apimachinery/pkg/api/errors"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33 "k8s.io/apimachinery/pkg/util/wait"
34 "k8s.io/client-go/kubernetes"
35 "k8s.io/client-go/rest"
36 "sigs.k8s.io/controller-runtime/pkg/client"
37 )
38
39 func DeleteAllEventsForUnstruct(t *testing.T, c client.Client, unstruct *unstructured.Unstructured) {
40 for _, e := range getEventsForObject(t, c, unstruct.GetKind(), unstruct.GetName(), unstruct.GetNamespace()) {
41 if err := c.Delete(context.TODO(), &e); err != nil {
42 t.Fatalf("unable to delete event for %v %v/%v: %v", unstruct.GetKind(), unstruct.GetNamespace(), unstruct.GetName(), err)
43 }
44 }
45 }
46
47 func AssertEventRecordedForObjectMetaAndKind(t *testing.T, c client.Client, kind string, om *metav1.ObjectMeta, reason string) {
48 assertEventRecorded(t, c, kind, om.Name, om.Namespace, reason)
49 }
50
51 func AssertEventRecordedforUnstruct(t *testing.T, c client.Client, unstruct *unstructured.Unstructured, reason string) {
52 assertEventRecorded(t, c, unstruct.GetKind(), unstruct.GetName(), unstruct.GetNamespace(), reason)
53 }
54
55 func AssertEventNotRecordedforUnstruct(t *testing.T, c client.Client, unstruct *unstructured.Unstructured, reason string) {
56 assertEventNotRecorded(t, c, unstruct.GetKind(), unstruct.GetName(), unstruct.GetNamespace(), reason)
57 }
58
59 func AssertObservedGenerationEquals(t *testing.T, unstruct *unstructured.Unstructured, preReconcileGeneration int64) {
60 observedGeneration, found, err := unstructured.NestedInt64(unstruct.Object, "status", "observedGeneration")
61 if err != nil {
62 t.Errorf("error getting the value for 'status.observedGeneration': %v", err)
63 }
64 if !found {
65 t.Errorf("'status.observedGeneration' is not found")
66 }
67 if observedGeneration != preReconcileGeneration {
68 t.Errorf("observedGeneration %v doesn't match with the pre-reconcile generation %v", observedGeneration, preReconcileGeneration)
69 }
70 }
71
72 func assertEventRecorded(t *testing.T, c client.Client, kind, name, namespace, reason string) {
73 err := waitUntilEventRecorded(t, c, kind, name, namespace, reason)
74 if err != nil {
75 t.Errorf("event with reason '%v' not recorded for %v %v/%v", reason, kind, namespace, name)
76 }
77 }
78
79 func assertEventNotRecorded(t *testing.T, c client.Client, kind, name, namespace, reason string) {
80 err := waitUntilEventRecorded(t, c, kind, name, namespace, reason)
81 if err == nil {
82 t.Errorf("expected event with reason '%v' to not be recorded for %v %v/%v, but it was", reason, kind, namespace, name)
83 } else if err != wait.ErrWaitTimeout {
84 t.Errorf("error waiting for event with reason '%v' to be recorded for %v %v/%v: %v", reason, kind, namespace, name, err)
85 }
86 }
87
88 func waitUntilEventRecorded(t *testing.T, c client.Client, kind, name, namespace, reason string) error {
89
90 interval := 10 * time.Second
91 timeout := 1 * time.Minute
92 return wait.PollImmediate(interval, timeout, func() (done bool, err error) {
93 return eventRecorded(t, c, kind, name, namespace, reason), nil
94 })
95 }
96
97 func eventRecorded(t *testing.T, c client.Client, kind, name, namespace, reason string) bool {
98 for _, e := range getEventsForObject(t, c, kind, name, namespace) {
99 if e.Reason == reason {
100 return true
101 }
102 }
103 return false
104 }
105
106 func getEventsForObject(t *testing.T, c client.Client, kind, name, namespace string) []v1.Event {
107 listOptions := client.ListOptions{
108 Namespace: namespace,
109 }
110 events := make([]v1.Event, 0)
111 for ok := true; ok; ok = listOptions.Continue != "" {
112 var eventList v1.EventList
113 if err := c.List(context.TODO(), &eventList, &listOptions); err != nil {
114 t.Fatalf("error listing events for %v %v/%v: %v", kind, namespace, name, err)
115 }
116 for _, e := range eventList.Items {
117 obj := &e.InvolvedObject
118 if (obj.Kind == kind) && (obj.Namespace == namespace) && (obj.Name == name) {
119 events = append(events, e)
120 }
121 }
122 listOptions.Continue = eventList.Continue
123 }
124 return events
125 }
126
127 func WaitForUnstructDeleteToFinish(t *testing.T, kubeClient client.Client, origUnstruct *unstructured.Unstructured) {
128 unstruct := origUnstruct.DeepCopy()
129 err := wait.PollImmediate(1*time.Second, 30*time.Second, func() (done bool, err error) {
130 err = kubeClient.Get(context.TODO(), k8s.GetNamespacedName(unstruct), unstruct)
131 if err == nil {
132 return false, nil
133 }
134 if errors.IsNotFound(err) {
135 return true, nil
136 }
137 return true, err
138 })
139 if err != nil {
140 t.Fatalf("error waiting for %v %v/%v to be deleted: %v", unstruct.GetKind(), unstruct.GetNamespace(), unstruct.GetName(), err)
141 }
142 }
143
144
145 func ReplaceTestVars(t *testing.T, b []byte, uniqueId string, project testgcp.GCPProject) []byte {
146 s := string(b)
147 s = strings.Replace(s, "${uniqueId}", uniqueId, -1)
148 s = strings.Replace(s, "${projectId}", project.ProjectID, -1)
149 if strings.Contains(s, "${projectNumber}") {
150 projectNumber := strconv.FormatInt(project.ProjectNumber, 10)
151 s = strings.Replace(s, "${projectNumber}", projectNumber, -1)
152 }
153
154 s = strings.Replace(s, fmt.Sprintf("folders/${%s}", testgcp.TestFolderId), fmt.Sprintf("folders/%s", testgcp.GetFolderID(t)), -1)
155 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestFolderId), fmt.Sprintf("\"%s\"", testgcp.GetFolderID(t)), -1)
156 s = strings.Replace(s, fmt.Sprintf("folders/${%s}", testgcp.TestFolder2Id), fmt.Sprintf("folders/%s", testgcp.GetFolder2ID(t)), -1)
157 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestFolder2Id), fmt.Sprintf("\"%s\"", testgcp.GetFolder2ID(t)), -1)
158 s = strings.Replace(s, fmt.Sprintf("organizations/${%s}", testgcp.TestOrgId), fmt.Sprintf("organizations/%s", testgcp.GetOrgID(t)), -1)
159 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestOrgId), fmt.Sprintf("\"%s\"", testgcp.GetOrgID(t)), -1)
160 s = strings.Replace(s, fmt.Sprintf("projects/${%s}", testgcp.TestDependentOrgProjectId), fmt.Sprintf("projects/%s", testgcp.GetDependentOrgProjectID(t)), -1)
161 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestDependentOrgProjectId), fmt.Sprintf("\"%s\"", testgcp.GetDependentOrgProjectID(t)), -1)
162 s = strings.Replace(s, fmt.Sprintf("projects/${%s}", testgcp.TestDependentFolderProjectId), fmt.Sprintf("projects/%s", testgcp.GetDependentFolderProjectID(t)), -1)
163 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestDependentFolderProjectId), fmt.Sprintf("\"%s\"", testgcp.GetDependentFolderProjectID(t)), -1)
164 s = strings.Replace(s, fmt.Sprintf("projects/${%s}", testgcp.TestDependentNoNetworkProjectId), fmt.Sprintf("projects/%s", testgcp.GetDependentNoNetworkProjectID(t)), -1)
165 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestDependentNoNetworkProjectId), fmt.Sprintf("\"%s\"", testgcp.GetDependentNoNetworkProjectID(t)), -1)
166 s = strings.Replace(s, fmt.Sprintf("organizations/${%s}", testgcp.IAMIntegrationTestsOrganizationId), fmt.Sprintf("organizations/%s", testgcp.GetIAMIntegrationTestsOrganizationId(t)), -1)
167 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.IAMIntegrationTestsOrganizationId), fmt.Sprintf("\"%s\"", testgcp.GetIAMIntegrationTestsOrganizationId(t)), -1)
168 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.IsolatedTestOrgName), testgcp.GetIsolatedTestOrgName(t), -1)
169 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestBillingAccountId), testgcp.GetBillingAccountID(t), -1)
170 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.TestBillingAccountIDForBillingResources), testgcp.GetTestBillingAccountIDForBillingResources(t), -1)
171 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.IAMIntegrationTestsBillingAccountId), testgcp.GetIAMIntegrationTestsBillingAccountId(t), -1)
172 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.FirestoreTestProject), testgcp.GetFirestoreTestProject(t), -1)
173 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.CloudFunctionsTestProject), testgcp.GetCloudFunctionsTestProject(t), -1)
174 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.IdentityPlatformTestProject), testgcp.GetIdentityPlatformTestProject(t), -1)
175 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.InterconnectTestProject), testgcp.GetInterconnectTestProject(t), -1)
176 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.HighCPUQuotaTestProject), testgcp.GetHighCpuQuotaTestProject(t), -1)
177 s = strings.Replace(s, fmt.Sprintf("${%s}", testgcp.RecaptchaEnterpriseTestProject), testgcp.GetRecaptchaEnterpriseTestProject(t), -1)
178 return []byte(s)
179 }
180
181
182
183 func CollectEvents(t *testing.T, config *rest.Config, namespace string, expectedCount int, timeout time.Duration) []v1.Event {
184 t.Helper()
185 clientSet, err := kubernetes.NewForConfig(config)
186 if err != nil {
187 t.Fatalf("error creating k8s client: %v", err)
188 }
189 listOptions := metav1.ListOptions{}
190 watcher, err := clientSet.CoreV1().Events(namespace).Watch(context.Background(), listOptions)
191 if err != nil {
192 t.Fatalf("errror creating event watch: %v", err)
193 }
194 defer watcher.Stop()
195 results := make([]v1.Event, 0)
196 ch := watcher.ResultChan()
197 for i := 0; i < expectedCount; i++ {
198 select {
199 case res := <-ch:
200 event, ok := res.Object.(*v1.Event)
201 if !ok {
202 t.Fatalf("unexpected type returned in channel: got '%v', watch '%v'", reflect.TypeOf(res), reflect.TypeOf(v1.Event{}))
203 }
204 results = append(results, *event)
205 case <-time.After(timeout):
206 t.Fatalf("expected '%v' event(s), collected '%v' event(s), timed out waiting for the last '%v' event(s)t'",
207 expectedCount, len(results), expectedCount-len(results))
208 }
209 }
210 return results
211 }
212
View as plain text