github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/operation/k8s/k8s_scheduler_test.go (about) 1 package k8s_test 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/kyma-incubator/compass/components/director/pkg/str" 9 "github.com/kyma-incubator/compass/components/operations-controller/api/v1alpha1" 10 "github.com/pkg/errors" 11 "github.com/stretchr/testify/require" 12 v1 "k8s.io/api/core/v1" 13 k8s_errors "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/runtime/schema" 16 17 "github.com/kyma-incubator/compass/components/director/pkg/operation" 18 "github.com/kyma-incubator/compass/components/director/pkg/operation/k8s" 19 "github.com/kyma-incubator/compass/components/director/pkg/operation/k8s/automock" 20 ) 21 22 const ( 23 resourceID = "c7092c57-7a5c-4ebe-8c58-03c0f85ade6c" 24 operationID = "0b4fc816-da70-4505-961e-db346388fdb7" 25 ) 26 27 func TestScheduler_Schedule(t *testing.T) { 28 t.Run("when the k8s client fails to retrieve the operation it should fail", func(t *testing.T) { 29 // GIVEN 30 ctx := context.TODO() 31 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 32 expErr := errors.New("error") 33 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 34 35 cli := &automock.K8SClient{} 36 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(nil, expErr).Once() 37 s := k8s.NewScheduler(cli) 38 39 // WHEN 40 _, err := s.Schedule(ctx, op) 41 // THEN 42 require.Equal(t, expErr, err) 43 }) 44 45 t.Run("when no previous operation exists and the k8s client fails to create a new one it should fail", func(t *testing.T) { 46 // GIVEN 47 ctx := context.TODO() 48 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 49 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 50 51 cli := &automock.K8SClient{} 52 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(nil, k8s_errors.NewNotFound(schema.GroupResource{}, "test")).Once() 53 54 k8sOp := toK8SOperation(op) 55 expErr := errors.New("error") 56 cli.On("Create", ctx, k8sOp).Return(nil, expErr).Once() 57 58 s := k8s.NewScheduler(cli) 59 60 // WHEN 61 _, err := s.Schedule(ctx, op) 62 // THEN 63 require.Error(t, err) 64 require.Equal(t, expErr, err) 65 }) 66 67 t.Run("when no previous operation exists it should return the ID of a newly created operation", func(t *testing.T) { 68 // GIVEN 69 ctx := context.TODO() 70 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 71 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 72 73 cli := &automock.K8SClient{} 74 notFoundErr := k8s_errors.NewNotFound(schema.GroupResource{}, operationName) 75 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(nil, notFoundErr).Once() 76 77 k8sOp := toK8SOperation(op) 78 k8sOp.UID = operationID 79 k8sOpWithoutID := toK8SOperation(op) 80 81 cli.On("Create", ctx, k8sOpWithoutID).Return(k8sOp, nil).Once() 82 83 s := k8s.NewScheduler(cli) 84 85 // WHEN 86 opID, err := s.Schedule(ctx, op) 87 // THEN 88 require.NoError(t, err) 89 require.Equal(t, string(k8sOp.UID), opID) 90 }) 91 92 t.Run("when a previous operation is in progress it should return an error", func(t *testing.T) { 93 // GIVEN 94 ctx := context.TODO() 95 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 96 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 97 98 cli := &automock.K8SClient{} 99 100 k8sOp := toK8SOperation(op) 101 k8sOp.UID = operationID 102 103 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(k8sOp, nil).Once() 104 105 s := k8s.NewScheduler(cli) 106 107 // WHEN 108 _, err := s.Schedule(ctx, op) 109 // THEN 110 require.Error(t, err) 111 require.Contains(t, err.Error(), fmt.Sprintf("another operation is in progress for resource with ID %q", op.ResourceID)) 112 }) 113 114 t.Run("when operation update fails it should return an error", func(t *testing.T) { 115 // GIVEN 116 ctx := context.TODO() 117 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 118 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 119 120 cli := &automock.K8SClient{} 121 122 k8sOp := toK8SOperation(op) 123 k8sOp.UID = operationID 124 k8sOp.Status.Conditions = []v1alpha1.Condition{{ 125 Status: v1.ConditionTrue, 126 }} 127 128 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(k8sOp, nil).Once() 129 expErr := errors.New("error") 130 cli.On("Update", ctx, k8sOp).Return(nil, expErr).Once() 131 132 defer cli.AssertExpectations(t) 133 s := k8s.NewScheduler(cli) 134 135 // WHEN 136 _, err := s.Schedule(ctx, op) 137 // THEN 138 require.Error(t, err) 139 require.Equal(t, expErr, err) 140 }) 141 142 t.Run("when another operation update is in progress it should return an error", func(t *testing.T) { 143 // GIVEN 144 ctx := context.TODO() 145 op := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 146 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 147 148 cli := &automock.K8SClient{} 149 150 k8sOp := toK8SOperation(op) 151 k8sOp.UID = operationID 152 k8sOp.Status.Conditions = []v1alpha1.Condition{{ 153 Status: v1.ConditionTrue, 154 }} 155 156 cli.On("Get", ctx, operationName, metav1.GetOptions{}).Return(k8sOp, nil).Once() 157 conflictErr := k8s_errors.NewConflict(schema.GroupResource{}, operationName, errors.New("error")) 158 cli.On("Update", ctx, k8sOp).Return(nil, conflictErr).Once() 159 160 defer cli.AssertExpectations(t) 161 s := k8s.NewScheduler(cli) 162 163 // WHEN 164 _, err := s.Schedule(ctx, op) 165 // THEN 166 require.Error(t, err) 167 require.Contains(t, err.Error(), fmt.Sprintf("another operation is in progress for resource with ID %q", op.ResourceID)) 168 }) 169 170 t.Run("when the operation is updated successfully it should return the ID of the operation", func(t *testing.T) { 171 // GIVEN 172 ctx := context.TODO() 173 cli := &automock.K8SClient{} 174 175 completedOp := &operation.Operation{OperationType: operation.OperationTypeCreate, ResourceID: resourceID} 176 completedOpName := fmt.Sprintf("%s-%s", completedOp.ResourceType, completedOp.ResourceID) 177 completedk8sOp := toK8SOperation(completedOp) 178 completedk8sOp.UID = operationID 179 completedk8sOp.Status.Conditions = []v1alpha1.Condition{{ 180 Status: v1.ConditionTrue, 181 }} 182 183 newOp := &operation.Operation{OperationType: operation.OperationTypeUpdate, ResourceID: resourceID} 184 newK8sOp := toK8SOperation(newOp) 185 newK8sOp.UID = operationID 186 187 cli.On("Get", ctx, completedOpName, metav1.GetOptions{}).Return(completedk8sOp, nil).Once() 188 cli.On("Update", ctx, completedk8sOp).Return(newK8sOp, nil).Once() 189 190 s := k8s.NewScheduler(cli) 191 192 // WHEN 193 opID, err := s.Schedule(ctx, newOp) 194 // THEN 195 require.NoError(t, err) 196 require.Equal(t, string(newK8sOp.UID), opID) 197 }) 198 } 199 200 func toK8SOperation(op *operation.Operation) *v1alpha1.Operation { 201 operationName := fmt.Sprintf("%s-%s", op.ResourceType, op.ResourceID) 202 return &v1alpha1.Operation{ 203 TypeMeta: metav1.TypeMeta{}, 204 ObjectMeta: metav1.ObjectMeta{ 205 Name: operationName, 206 }, 207 Spec: v1alpha1.OperationSpec{ 208 OperationType: v1alpha1.OperationType(str.Title(string(op.OperationType))), 209 ResourceID: op.ResourceID, 210 }, 211 Status: v1alpha1.OperationStatus{}, 212 } 213 }