github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/process/operation_manager.go (about)

     1  package process
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/kyma-project/kyma-environment-broker/common/orchestration"
     8  	"github.com/kyma-project/kyma-environment-broker/internal"
     9  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    10  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    11  	"github.com/pivotal-cf/brokerapi/v8/domain"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  type OperationManager struct {
    16  	storage storage.Operations
    17  }
    18  
    19  func NewOperationManager(storage storage.Operations) *OperationManager {
    20  	return &OperationManager{storage: storage}
    21  }
    22  
    23  // OperationSucceeded marks the operation as succeeded and returns status of the operation's update
    24  func (om *OperationManager) OperationSucceeded(operation internal.Operation, description string, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    25  	return om.update(operation, domain.Succeeded, description, log)
    26  }
    27  
    28  // OperationFailed marks the operation as failed and returns status of the operation's update
    29  func (om *OperationManager) OperationFailed(operation internal.Operation, description string, err error, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    30  	op, t, _ := om.update(operation, domain.Failed, description, log)
    31  	// repeat in case of storage error
    32  	if t != 0 {
    33  		return op, t, nil
    34  	}
    35  
    36  	var retErr error
    37  	if err == nil {
    38  		// no exact err passed in
    39  		retErr = fmt.Errorf(description)
    40  	} else {
    41  		// keep the original err object for error categorizer
    42  		retErr = fmt.Errorf("%s: %w", description, err)
    43  	}
    44  	operation.EventErrorf(err, "operation failed")
    45  
    46  	return op, 0, retErr
    47  }
    48  
    49  // OperationCanceled marks the operation as canceled and returns status of the operation's update
    50  func (om *OperationManager) OperationCanceled(operation internal.Operation, description string, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    51  	return om.update(operation, orchestration.Canceled, description, log)
    52  }
    53  
    54  // RetryOperation checks if operation should be retried or if it's the status should be marked as failed
    55  func (om *OperationManager) RetryOperation(operation internal.Operation, errorMessage string, err error, retryInterval time.Duration, maxTime time.Duration, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    56  	log.Infof("Retry Operation was triggered with message: %s", errorMessage)
    57  	log.Infof("Retrying for %s in %s steps", maxTime.String(), retryInterval.String())
    58  	if time.Since(operation.UpdatedAt) < maxTime {
    59  		return operation, retryInterval, nil
    60  	}
    61  	log.Errorf("Aborting after %s of failing retries", maxTime.String())
    62  	op, retry, err := om.OperationFailed(operation, errorMessage, err, log)
    63  	if err == nil {
    64  		err = fmt.Errorf("Too many retries")
    65  	} else {
    66  		err = fmt.Errorf("Failed to set status for operation after too many retries: %v", err)
    67  	}
    68  	return op, retry, err
    69  }
    70  
    71  // RetryOperationWithoutFail checks if operation should be retried or updates the status to InProgress, but omits setting the operation to failed if maxTime is reached
    72  func (om *OperationManager) RetryOperationWithoutFail(operation internal.Operation, stepName string, description string, retryInterval, maxTime time.Duration, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    73  	log.Infof("Retry Operation was triggered with message: %s", description)
    74  	log.Infof("Retrying for %s in %s steps", maxTime.String(), retryInterval.String())
    75  	if time.Since(operation.UpdatedAt) < maxTime {
    76  		return operation, retryInterval, nil
    77  	}
    78  	// update description to track failed steps
    79  	op, repeat, err := om.UpdateOperation(operation, func(operation *internal.Operation) {
    80  		operation.State = domain.InProgress
    81  		operation.Description = description
    82  		operation.ExcutedButNotCompleted = append(operation.ExcutedButNotCompleted, stepName)
    83  	}, log)
    84  	if repeat != 0 {
    85  		return op, repeat, err
    86  	}
    87  
    88  	op.EventErrorf(fmt.Errorf(description), "step %s failed retries: operation continues", stepName)
    89  	log.Errorf("Omitting after %s of failing retries", maxTime.String())
    90  	return op, 0, nil
    91  }
    92  
    93  // RetryOperationOnce retries the operation once and fails the operation when call second time
    94  func (om *OperationManager) RetryOperationOnce(operation internal.Operation, errorMessage string, err error, wait time.Duration, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
    95  	return om.RetryOperation(operation, errorMessage, err, wait, wait+1, log)
    96  }
    97  
    98  // UpdateOperation updates a given operation and handles conflict situation
    99  func (om *OperationManager) UpdateOperation(operation internal.Operation, update func(operation *internal.Operation), log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
   100  	update(&operation)
   101  	op, err := om.storage.UpdateOperation(operation)
   102  	switch {
   103  	case dberr.IsConflict(err):
   104  		{
   105  			op, err = om.storage.GetOperationByID(operation.ID)
   106  			if err != nil {
   107  				log.Errorf("while getting operation: %v", err)
   108  				return operation, 1 * time.Minute, err
   109  			}
   110  			update(op)
   111  			op, err = om.storage.UpdateOperation(*op)
   112  			if err != nil {
   113  				log.Errorf("while updating operation after conflict: %v", err)
   114  				return operation, 1 * time.Minute, err
   115  			}
   116  		}
   117  	case err != nil:
   118  		log.Errorf("while updating operation: %v", err)
   119  		return operation, 1 * time.Minute, err
   120  	}
   121  
   122  	return *op, 0, nil
   123  }
   124  
   125  func (om *OperationManager) MarkStepAsExcutedButNotCompleted(operation internal.Operation, stepName string, msg string, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
   126  	op, repeat, err := om.UpdateOperation(operation, func(operation *internal.Operation) {
   127  		operation.ExcutedButNotCompleted = append(operation.ExcutedButNotCompleted, stepName)
   128  	}, log)
   129  	if repeat != 0 {
   130  		return op, repeat, err
   131  	}
   132  
   133  	op.EventErrorf(fmt.Errorf(msg), "step %s failed: operation continues", stepName)
   134  	log.Errorf(msg)
   135  	return op, 0, nil
   136  }
   137  
   138  func (om *OperationManager) update(operation internal.Operation, state domain.LastOperationState, description string, log logrus.FieldLogger) (internal.Operation, time.Duration, error) {
   139  	return om.UpdateOperation(operation, func(operation *internal.Operation) {
   140  		operation.State = state
   141  		operation.Description = description
   142  	}, log)
   143  }