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 }