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  }