github.com/banzaicloud/operator-tools@v0.28.10/pkg/reconciler/resource_test.go (about) 1 // Copyright © 2020 Banzai Cloud 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package reconciler_test 16 17 import ( 18 "context" 19 "testing" 20 21 "github.com/banzaicloud/operator-tools/pkg/reconciler" 22 "github.com/banzaicloud/operator-tools/pkg/utils" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 appsv1 "k8s.io/api/apps/v1" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/types" 31 "sigs.k8s.io/controller-runtime/pkg/reconcile" 32 ) 33 34 func TestNewReconcilerWith(t *testing.T) { 35 desired := &corev1.ConfigMap{ 36 ObjectMeta: metav1.ObjectMeta{ 37 Name: "test", 38 Namespace: controlNamespace, 39 }, 40 Data: map[string]string{ 41 "a": "b", 42 }, 43 } 44 r := reconciler.NewReconcilerWith(k8sClient, reconciler.WithEnableRecreateWorkload()) 45 result, err := r.ReconcileResource(desired, reconciler.StatePresent) 46 if result != nil { 47 t.Fatalf("result expected to be nil if everything went smooth") 48 } 49 if err != nil { 50 t.Fatalf("%+v", err) 51 } 52 53 created := &corev1.ConfigMap{} 54 if err := k8sClient.Get(context.TODO(), utils.ObjectKeyFromObjectMeta(desired), created); err != nil { 55 t.Fatalf("%+v", err) 56 } 57 58 assert.Equal(t, created.Name, desired.Name) 59 assert.Equal(t, created.Namespace, desired.Namespace) 60 } 61 62 func TestNewReconcilerWithUnstructured(t *testing.T) { 63 desired := &unstructured.Unstructured{ 64 Object: map[string]interface{}{ 65 "metadata": map[string]interface{}{ 66 "name": "test", 67 "namespace": controlNamespace, 68 }, 69 "data": map[string]string{ 70 "a": "b", 71 }, 72 }, 73 } 74 desired.SetAPIVersion("v1") 75 desired.SetKind("ConfigMap") 76 r := reconciler.NewReconcilerWith(k8sClient, reconciler.WithEnableRecreateWorkload(), reconciler.WithLog(utils.Log)) 77 result, err := r.ReconcileResource(desired, reconciler.StatePresent) 78 if result != nil { 79 t.Fatalf("result expected to be nil if everything went smooth") 80 } 81 if err != nil { 82 t.Fatalf("%+v", err) 83 } 84 85 created := &corev1.ConfigMap{} 86 if err := k8sClient.Get(context.TODO(), utils.ObjectKeyFromObjectMeta(desired), created); err != nil { 87 t.Fatalf("%+v", err) 88 } 89 90 assert.Equal(t, created.Name, "test") 91 assert.Equal(t, created.Namespace, controlNamespace) 92 } 93 94 func TestRecreateObjectFailIfNotAllowed(t *testing.T) { 95 testData := []struct { 96 name string 97 desired runtime.Object 98 reconciler reconciler.ResourceReconciler 99 update func(object runtime.Object) runtime.Object 100 wantError func(error) 101 wantResult func(result *reconcile.Result) 102 }{ 103 { 104 name: "fails to recreate service", 105 desired: &corev1.Service{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Name: "test0", 108 Namespace: testNamespace, 109 }, 110 Spec: corev1.ServiceSpec{ 111 ClusterIP: "10.0.0.10", 112 Ports: []corev1.ServicePort{ 113 { 114 Port: 123, 115 }, 116 }, 117 }, 118 }, 119 reconciler: reconciler.NewReconcilerWith(k8sClient, 120 reconciler.WithEnableRecreateWorkload(), 121 reconciler.WithRecreateEnabledForNothing(), 122 ), 123 update: func(object runtime.Object) runtime.Object { 124 object.(*corev1.Service).Spec.ClusterIP = "10.0.0.11" 125 return object 126 }, 127 wantError: func(err error) { 128 require.Error(t, err) 129 require.Contains(t, err.Error(), "may not change once set") 130 }, 131 }, 132 { 133 name: "allowed to recreate service by default", 134 desired: &corev1.Service{ 135 ObjectMeta: metav1.ObjectMeta{ 136 Name: "test1", 137 Namespace: testNamespace, 138 }, 139 Spec: corev1.ServiceSpec{ 140 ClusterIP: "10.0.0.20", 141 Ports: []corev1.ServicePort{ 142 { 143 Port: 123, 144 }, 145 }, 146 }, 147 }, 148 reconciler: reconciler.NewReconcilerWith(k8sClient, 149 reconciler.WithEnableRecreateWorkload(), 150 ), 151 update: func(object runtime.Object) runtime.Object { 152 object.(*corev1.Service).Spec.ClusterIP = "10.0.0.21" 153 return object 154 }, 155 wantResult: func(result *reconcile.Result) { 156 require.NotNil(t, result) 157 require.True(t, result.Requeue) 158 }, 159 }, 160 { 161 name: "recreate service immediately", 162 desired: &corev1.Service{ 163 ObjectMeta: metav1.ObjectMeta{ 164 Name: "test2", 165 Namespace: testNamespace, 166 }, 167 Spec: corev1.ServiceSpec{ 168 ClusterIP: "10.0.0.31", 169 Ports: []corev1.ServicePort{ 170 { 171 Port: 123, 172 }, 173 }, 174 }, 175 }, 176 reconciler: reconciler.NewReconcilerWith(k8sClient, 177 reconciler.WithEnableRecreateWorkload(), 178 reconciler.WithRecreateImmediately(), 179 ), 180 update: func(object runtime.Object) runtime.Object { 181 object.(*corev1.Service).Spec.ClusterIP = "None" 182 object.(*corev1.Service).Spec.ClusterIPs = []string{"None"} 183 return object 184 }, 185 wantResult: func(result *reconcile.Result) { 186 require.Nil(t, result) 187 svc := &corev1.Service{} 188 err := k8sClient.Get(context.TODO(), types.NamespacedName{ 189 Namespace: testNamespace, 190 Name: "test2", 191 }, svc) 192 require.NoError(t, err) 193 require.Equal(t, svc.Spec.ClusterIP, "None") 194 }, 195 }, 196 { 197 name: "recreate statefulset", 198 desired: &appsv1.StatefulSet{ 199 ObjectMeta: metav1.ObjectMeta{ 200 Name: "test", 201 Namespace: testNamespace, 202 }, 203 Spec: appsv1.StatefulSetSpec{ 204 Selector: &metav1.LabelSelector{ 205 MatchLabels: map[string]string{ 206 "app": "test", 207 }, 208 }, 209 Template: corev1.PodTemplateSpec{ 210 ObjectMeta: metav1.ObjectMeta{ 211 Labels: map[string]string{ 212 "app": "test", 213 }, 214 }, 215 Spec: corev1.PodSpec{ 216 Containers: []corev1.Container{ 217 { 218 Name: "test", 219 Image: "test", 220 }, 221 }, 222 }, 223 }, 224 ServiceName: "test", 225 }, 226 }, 227 reconciler: reconciler.NewReconcilerWith(k8sClient, 228 reconciler.WithEnableRecreateWorkload(), 229 reconciler.WithRecreateErrorMessageCondition(reconciler.MatchImmutableErrorMessages), 230 reconciler.WithRecreateImmediately(), 231 ), 232 update: func(object runtime.Object) runtime.Object { 233 object.(*appsv1.StatefulSet).Spec.ServiceName = "test2" 234 return object 235 }, 236 wantResult: func(result *reconcile.Result) { 237 require.Nil(t, result) 238 statefulSet := &appsv1.StatefulSet{} 239 err := k8sClient.Get(context.TODO(), types.NamespacedName{ 240 Namespace: testNamespace, 241 Name: "test", 242 }, statefulSet) 243 require.NoError(t, err) 244 require.Equal(t, statefulSet.Spec.ServiceName, "test2") 245 }, 246 }, 247 } 248 249 for _, tt := range testData { 250 tt := tt 251 t.Run(tt.name, func(t *testing.T) { 252 _, err := tt.reconciler.ReconcileResource(tt.desired, reconciler.StatePresent) 253 require.NoError(t, err) 254 255 result, err := tt.reconciler.ReconcileResource(tt.update(tt.desired), reconciler.StatePresent) 256 if tt.wantError != nil { 257 tt.wantError(err) 258 } else { 259 require.NoError(t, err) 260 } 261 if tt.wantResult != nil { 262 tt.wantResult(result) 263 } else { 264 require.Nil(t, result) 265 } 266 }) 267 } 268 }