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

     1  package suspension
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/google/uuid"
     8  	"github.com/kyma-project/kyma-environment-broker/common/orchestration"
     9  	"github.com/kyma-project/kyma-environment-broker/internal"
    10  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    13  	"github.com/pivotal-cf/brokerapi/v8/domain"
    14  	"github.com/pivotal-cf/brokerapi/v8/domain/apiresponses"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type ContextUpdateHandler struct {
    19  	operations          storage.Operations
    20  	provisioningQueue   Adder
    21  	deprovisioningQueue Adder
    22  
    23  	log logrus.FieldLogger
    24  }
    25  
    26  type Adder interface {
    27  	Add(processId string)
    28  }
    29  
    30  func NewContextUpdateHandler(operations storage.Operations, provisioningQueue Adder, deprovisioningQueue Adder, l logrus.FieldLogger) *ContextUpdateHandler {
    31  	return &ContextUpdateHandler{
    32  		operations:          operations,
    33  		provisioningQueue:   provisioningQueue,
    34  		deprovisioningQueue: deprovisioningQueue,
    35  		log:                 l,
    36  	}
    37  }
    38  
    39  // Handle performs suspension/unsuspension for given instance.
    40  // Applies only when 'Active' parameter has changes and ServicePlanID is `Trial`
    41  func (h *ContextUpdateHandler) Handle(instance *internal.Instance, newCtx internal.ERSContext) (bool, error) {
    42  	l := h.log.WithFields(logrus.Fields{
    43  		"instanceID":      instance.InstanceID,
    44  		"runtimeID":       instance.RuntimeID,
    45  		"globalAccountID": instance.GlobalAccountID,
    46  	})
    47  
    48  	if !broker.IsTrialPlan(instance.ServicePlanID) {
    49  		l.Info("Context update for non-trial instance, skipping")
    50  		return false, nil
    51  	}
    52  
    53  	return h.handleContextChange(newCtx, instance, l)
    54  }
    55  
    56  func (h *ContextUpdateHandler) handleContextChange(newCtx internal.ERSContext, instance *internal.Instance, l logrus.FieldLogger) (bool, error) {
    57  	isActivated := true
    58  	if instance.Parameters.ErsContext.Active != nil {
    59  		isActivated = *instance.Parameters.ErsContext.Active
    60  	}
    61  
    62  	lastDeprovisioning, err := h.operations.GetDeprovisioningOperationByInstanceID(instance.InstanceID)
    63  	// there was an error - fail
    64  	if err != nil && !dberr.IsNotFound(err) {
    65  		return false, err
    66  	}
    67  
    68  	if newCtx.Active == nil || isActivated == *newCtx.Active {
    69  		l.Debugf("Context.Active flag was not changed, the current value: %v", isActivated)
    70  		if isActivated {
    71  			// instance is marked as Active and incoming context update is unsuspension
    72  			// TODO: consider retriggering failed unsuspension here
    73  			return false, nil
    74  		}
    75  		if !isActivated {
    76  			// instance is inactive and incoming context update is suspension - verify if KEB should retrigger the operation
    77  			if lastDeprovisioning.State == domain.Failed {
    78  				l.Infof("Retriggering suspension for instance id %s", instance.InstanceID)
    79  				return true, h.suspend(instance, l)
    80  			}
    81  			return false, nil
    82  		}
    83  	}
    84  
    85  	if *newCtx.Active {
    86  		if instance.IsExpired() {
    87  			// if the instance is expired - do nothing
    88  			return false, nil
    89  		}
    90  		if lastDeprovisioning != nil && !lastDeprovisioning.Temporary {
    91  			l.Infof("Instance has a deprovisioning operation %s (%s), skipping unsuspension.", lastDeprovisioning.ID, lastDeprovisioning.State)
    92  			return false, nil
    93  		}
    94  		if lastDeprovisioning != nil && lastDeprovisioning.State == domain.Failed {
    95  			err := fmt.Errorf("Preceding suspension has failed, unable to reliably unsuspend")
    96  			return false, apiresponses.NewFailureResponse(err, http.StatusInternalServerError, "provisioning")
    97  		}
    98  		return true, h.unsuspend(instance, l)
    99  	} else {
   100  		return true, h.suspend(instance, l)
   101  	}
   102  }
   103  
   104  func (h *ContextUpdateHandler) suspend(instance *internal.Instance, log logrus.FieldLogger) error {
   105  	lastDeprovisioning, err := h.operations.GetDeprovisioningOperationByInstanceID(instance.InstanceID)
   106  	// there was an error - fail
   107  	if err != nil && !dberr.IsNotFound(err) {
   108  		return err
   109  	}
   110  
   111  	// no error, operation exists and is in progress
   112  	if err == nil && (lastDeprovisioning.State == domain.InProgress || lastDeprovisioning.State == orchestration.Pending) {
   113  		log.Infof("Suspension already started")
   114  		return nil
   115  	}
   116  
   117  	id := uuid.New().String()
   118  	operation := internal.NewSuspensionOperationWithID(id, instance)
   119  	err = h.operations.InsertDeprovisioningOperation(operation)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	h.deprovisioningQueue.Add(operation.ID)
   124  	return nil
   125  }
   126  
   127  func (h *ContextUpdateHandler) unsuspend(instance *internal.Instance, log logrus.FieldLogger) error {
   128  	if instance.IsExpired() {
   129  		log.Info("Expired instance cannot be unsuspended")
   130  		return nil
   131  	}
   132  	id := uuid.New().String()
   133  	operation, err := internal.NewProvisioningOperationWithID(id, instance.InstanceID, instance.Parameters)
   134  	operation.InstanceDetails, err = instance.GetInstanceDetails()
   135  
   136  	if err != nil {
   137  		h.log.Errorf("unable to extract shoot name: %s", err.Error())
   138  		return err
   139  	}
   140  	operation.State = orchestration.Pending
   141  	log.Infof("Starting unsuspension: shootName=%s shootDomain=%s", operation.ShootName, operation.ShootDomain)
   142  	// RuntimeID must be cleaned  - this mean that there is no runtime in the provisioner/director
   143  	operation.RuntimeID = ""
   144  	operation.DashboardURL = instance.DashboardURL
   145  
   146  	err = h.operations.InsertProvisioningOperation(operation)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	h.provisioningQueue.Add(operation.ID)
   151  	return nil
   152  }