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  }