k8s.io/kubernetes@v1.29.3/pkg/controller/certificates/rootcacertpublisher/publisher_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package rootcacertpublisher 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/client-go/informers" 29 "k8s.io/client-go/kubernetes/fake" 30 corev1listers "k8s.io/client-go/listers/core/v1" 31 clienttesting "k8s.io/client-go/testing" 32 "k8s.io/client-go/tools/cache" 33 "k8s.io/kubernetes/pkg/controller" 34 ) 35 36 func TestConfigMapCreation(t *testing.T) { 37 ns := metav1.NamespaceDefault 38 fakeRootCA := []byte("fake-root-ca") 39 40 caConfigMap := defaultCrtConfigMapPtr(fakeRootCA) 41 addFieldCM := defaultCrtConfigMapPtr(fakeRootCA) 42 addFieldCM.Data["test"] = "test" 43 modifyFieldCM := defaultCrtConfigMapPtr([]byte("abc")) 44 otherConfigMap := &v1.ConfigMap{ 45 ObjectMeta: metav1.ObjectMeta{ 46 Name: "other", 47 Namespace: ns, 48 ResourceVersion: "1", 49 }, 50 } 51 updateOtherConfigMap := &v1.ConfigMap{ 52 ObjectMeta: metav1.ObjectMeta{ 53 Name: "other", 54 Namespace: ns, 55 ResourceVersion: "1", 56 }, 57 Data: map[string]string{"aa": "bb"}, 58 } 59 60 existNS := &v1.Namespace{ 61 ObjectMeta: metav1.ObjectMeta{Name: ns}, 62 Status: v1.NamespaceStatus{ 63 Phase: v1.NamespaceActive, 64 }, 65 } 66 newNs := &v1.Namespace{ 67 ObjectMeta: metav1.ObjectMeta{Name: "new"}, 68 Status: v1.NamespaceStatus{ 69 Phase: v1.NamespaceActive, 70 }, 71 } 72 terminatingNS := &v1.Namespace{ 73 ObjectMeta: metav1.ObjectMeta{Name: ns}, 74 Status: v1.NamespaceStatus{ 75 Phase: v1.NamespaceTerminating, 76 }, 77 } 78 79 type action struct { 80 verb string 81 name string 82 } 83 testcases := map[string]struct { 84 ExistingConfigMaps []*v1.ConfigMap 85 AddedNamespace *v1.Namespace 86 UpdatedNamespace *v1.Namespace 87 DeletedConfigMap *v1.ConfigMap 88 UpdatedConfigMap *v1.ConfigMap 89 ExpectActions []action 90 }{ 91 "create new namespace": { 92 AddedNamespace: newNs, 93 ExpectActions: []action{{verb: "create", name: RootCACertConfigMapName}}, 94 }, 95 "delete other configmap": { 96 ExistingConfigMaps: []*v1.ConfigMap{otherConfigMap, caConfigMap}, 97 DeletedConfigMap: otherConfigMap, 98 }, 99 "delete ca configmap": { 100 ExistingConfigMaps: []*v1.ConfigMap{otherConfigMap, caConfigMap}, 101 DeletedConfigMap: caConfigMap, 102 ExpectActions: []action{{verb: "create", name: RootCACertConfigMapName}}, 103 }, 104 "update ca configmap with adding field": { 105 ExistingConfigMaps: []*v1.ConfigMap{caConfigMap}, 106 UpdatedConfigMap: addFieldCM, 107 ExpectActions: []action{{verb: "update", name: RootCACertConfigMapName}}, 108 }, 109 "update ca configmap with modifying field": { 110 ExistingConfigMaps: []*v1.ConfigMap{caConfigMap}, 111 UpdatedConfigMap: modifyFieldCM, 112 ExpectActions: []action{{verb: "update", name: RootCACertConfigMapName}}, 113 }, 114 "update with other configmap": { 115 ExistingConfigMaps: []*v1.ConfigMap{caConfigMap, otherConfigMap}, 116 UpdatedConfigMap: updateOtherConfigMap, 117 }, 118 "update namespace with terminating state": { 119 UpdatedNamespace: terminatingNS, 120 }, 121 } 122 123 for k, tc := range testcases { 124 t.Run(k, func(t *testing.T) { 125 client := fake.NewSimpleClientset(caConfigMap, existNS) 126 informers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), controller.NoResyncPeriodFunc()) 127 cmInformer := informers.Core().V1().ConfigMaps() 128 nsInformer := informers.Core().V1().Namespaces() 129 controller, err := NewPublisher(cmInformer, nsInformer, client, fakeRootCA) 130 if err != nil { 131 t.Fatalf("error creating ServiceAccounts controller: %v", err) 132 } 133 134 cmStore := cmInformer.Informer().GetStore() 135 136 controller.syncHandler = controller.syncNamespace 137 138 for _, s := range tc.ExistingConfigMaps { 139 cmStore.Add(s) 140 } 141 142 if tc.AddedNamespace != nil { 143 controller.namespaceAdded(tc.AddedNamespace) 144 } 145 if tc.UpdatedNamespace != nil { 146 controller.namespaceUpdated(nil, tc.UpdatedNamespace) 147 } 148 149 if tc.DeletedConfigMap != nil { 150 cmStore.Delete(tc.DeletedConfigMap) 151 controller.configMapDeleted(tc.DeletedConfigMap) 152 } 153 154 if tc.UpdatedConfigMap != nil { 155 cmStore.Add(tc.UpdatedConfigMap) 156 controller.configMapUpdated(nil, tc.UpdatedConfigMap) 157 } 158 ctx := context.TODO() 159 for controller.queue.Len() != 0 { 160 controller.processNextWorkItem(ctx) 161 } 162 163 actions := client.Actions() 164 if reflect.DeepEqual(actions, tc.ExpectActions) { 165 t.Errorf("Unexpected actions:\n%s", cmp.Diff(actions, tc.ExpectActions)) 166 } 167 }) 168 } 169 } 170 171 func defaultCrtConfigMapPtr(rootCA []byte) *v1.ConfigMap { 172 tmp := v1.ConfigMap{ 173 ObjectMeta: metav1.ObjectMeta{ 174 Name: RootCACertConfigMapName, 175 }, 176 Data: map[string]string{ 177 "ca.crt": string(rootCA), 178 }, 179 } 180 tmp.Namespace = metav1.NamespaceDefault 181 return &tmp 182 } 183 184 func TestConfigMapUpdateNoHotLoop(t *testing.T) { 185 testcases := map[string]struct { 186 ExistingConfigMaps []runtime.Object 187 ExpectActions func(t *testing.T, actions []clienttesting.Action) 188 }{ 189 "update-configmap-annotation": { 190 ExistingConfigMaps: []runtime.Object{ 191 &v1.ConfigMap{ 192 ObjectMeta: metav1.ObjectMeta{ 193 Namespace: "default", 194 Name: RootCACertConfigMapName, 195 }, 196 Data: map[string]string{"ca.crt": "fake"}, 197 }, 198 }, 199 ExpectActions: func(t *testing.T, actions []clienttesting.Action) { 200 if len(actions) != 1 { 201 t.Fatal(actions) 202 } 203 if actions[0].GetVerb() != "update" { 204 t.Fatal(actions) 205 } 206 actualObj := actions[0].(clienttesting.UpdateAction).GetObject() 207 if actualObj.(*v1.ConfigMap).Annotations[DescriptionAnnotation] != Description { 208 t.Fatal(actions) 209 } 210 if !reflect.DeepEqual(actualObj.(*v1.ConfigMap).Data["ca.crt"], "fake") { 211 t.Fatal(actions) 212 } 213 }, 214 }, 215 "no-update-configmap-if-annotation-present-and-equal": { 216 ExistingConfigMaps: []runtime.Object{ 217 &v1.ConfigMap{ 218 ObjectMeta: metav1.ObjectMeta{ 219 Namespace: "default", 220 Name: RootCACertConfigMapName, 221 Annotations: map[string]string{DescriptionAnnotation: Description}, 222 }, 223 Data: map[string]string{"ca.crt": "fake"}, 224 }, 225 }, 226 ExpectActions: func(t *testing.T, actions []clienttesting.Action) { 227 if len(actions) != 0 { 228 t.Fatal(actions) 229 } 230 }, 231 }, 232 "no-update-configmap-if-annotation-present-and-different": { 233 ExistingConfigMaps: []runtime.Object{ 234 &v1.ConfigMap{ 235 ObjectMeta: metav1.ObjectMeta{ 236 Namespace: "default", 237 Name: RootCACertConfigMapName, 238 Annotations: map[string]string{DescriptionAnnotation: "different"}, 239 }, 240 Data: map[string]string{"ca.crt": "fake"}, 241 }, 242 }, 243 ExpectActions: func(t *testing.T, actions []clienttesting.Action) { 244 if len(actions) != 0 { 245 t.Fatal(actions) 246 } 247 }, 248 }, 249 } 250 251 for k, tc := range testcases { 252 t.Run(k, func(t *testing.T) { 253 client := fake.NewSimpleClientset(tc.ExistingConfigMaps...) 254 configMapIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) 255 for _, obj := range tc.ExistingConfigMaps { 256 configMapIndexer.Add(obj) 257 } 258 259 // Publisher manages certificate ConfigMap objects inside Namespaces 260 controller := Publisher{ 261 client: client, 262 rootCA: []byte("fake"), 263 cmLister: corev1listers.NewConfigMapLister(configMapIndexer), 264 cmListerSynced: func() bool { return true }, 265 nsListerSynced: func() bool { return true }, 266 } 267 ctx := context.TODO() 268 err := controller.syncNamespace(ctx, "default") 269 if err != nil { 270 t.Fatal(err) 271 } 272 tc.ExpectActions(t, client.Actions()) 273 }) 274 } 275 }