github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/process/update/manager.go (about) 1 package update 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 reconcilerApi "github.com/kyma-incubator/reconciler/pkg/keb" 10 "github.com/kyma-project/kyma-environment-broker/internal" 11 "github.com/kyma-project/kyma-environment-broker/internal/event" 12 "github.com/kyma-project/kyma-environment-broker/internal/process" 13 "github.com/kyma-project/kyma-environment-broker/internal/process/input" 14 "github.com/kyma-project/kyma-environment-broker/internal/storage" 15 "github.com/pivotal-cf/brokerapi/v8/domain" 16 "github.com/sirupsen/logrus" 17 ) 18 19 type Manager struct { 20 log logrus.FieldLogger 21 operationStorage storage.Operations 22 publisher event.Publisher 23 24 stages []*stage 25 operationTimeout time.Duration 26 27 mu sync.RWMutex 28 29 speedFactor int64 30 } 31 32 type Step interface { 33 Name() string 34 Run(operation internal.UpdatingOperation, logger logrus.FieldLogger) (internal.UpdatingOperation, time.Duration, error) 35 } 36 37 type StepCondition func(operation internal.UpdatingOperation) bool 38 39 type StepWithCondition struct { 40 Step 41 condition StepCondition 42 } 43 44 type stage struct { 45 name string 46 steps []StepWithCondition 47 } 48 49 func (s *stage) AddStep(step Step, cnd StepCondition) { 50 s.steps = append(s.steps, StepWithCondition{ 51 Step: step, 52 condition: cnd, 53 }) 54 } 55 56 func NewManager(storage storage.Operations, pub event.Publisher, operationTimeout time.Duration, logger logrus.FieldLogger) *Manager { 57 return &Manager{ 58 log: logger, 59 operationStorage: storage, 60 publisher: pub, 61 operationTimeout: operationTimeout, 62 speedFactor: 1, 63 } 64 } 65 66 // SpeedUp changes speedFactor parameter to reduce the sleep time if a step needs a retry. 67 // This method should only be used for testing purposes 68 func (m *Manager) SpeedUp(speedFactor int64) { 69 m.speedFactor = speedFactor 70 } 71 72 func (m *Manager) DefineStages(names []string) { 73 m.stages = make([]*stage, len(names)) 74 for i, n := range names { 75 m.stages[i] = &stage{name: n, steps: []StepWithCondition{}} 76 } 77 } 78 79 func (m *Manager) AddStep(stageName string, step Step, cnd StepCondition) error { 80 for _, s := range m.stages { 81 if s.name == stageName { 82 s.AddStep(step, cnd) 83 return nil 84 } 85 } 86 return fmt.Errorf("Stage %s not defined", stageName) 87 } 88 89 func (m *Manager) GetAllStages() []string { 90 var all []string 91 for _, s := range m.stages { 92 all = append(all, s.name) 93 } 94 return all 95 } 96 97 func (m *Manager) Execute(operationID string) (time.Duration, error) { 98 operation, err := m.operationStorage.GetUpdatingOperationByID(operationID) 99 if err != nil { 100 m.log.Errorf("Cannot fetch operation from storage: %s", err) 101 return 3 * time.Second, nil 102 } 103 104 logOperation := m.log.WithFields(logrus.Fields{"operation": operationID, "instanceID": operation.InstanceID, "planID": operation.ProvisioningParameters.PlanID}) 105 logOperation.Infof("Start process operation steps for GlobalAccount=%s, ", operation.ProvisioningParameters.ErsContext.GlobalAccountID) 106 if time.Since(operation.CreatedAt) > m.operationTimeout { 107 logOperation.Infof("operation has reached the time limit: operation was created at: %s", operation.CreatedAt) 108 operation.State = domain.Failed 109 _, err = m.operationStorage.UpdateUpdatingOperation(*operation) 110 if err != nil { 111 logOperation.Infof("Unable to save operation with finished the provisioning process") 112 return time.Second, err 113 } 114 return 0, fmt.Errorf("operation has reached the time limit") 115 } 116 117 var when time.Duration 118 processedOperation := *operation 119 120 for _, stage := range m.stages { 121 if processedOperation.IsStageFinished(stage.name) { 122 continue 123 } 124 125 for _, step := range stage.steps { 126 127 logStep := logOperation.WithField("step", step.Name()). 128 WithField("stage", stage.name) 129 if step.condition != nil && !step.condition(processedOperation) { 130 logStep.Debugf("Skipping due to condition not met") 131 continue 132 } 133 logStep.Infof("Start step") 134 135 processedOperation, when, err = m.runStep(step, processedOperation, logStep) 136 if err != nil { 137 logStep.Errorf("Process operation failed: %s", err) 138 return 0, err 139 } 140 if processedOperation.State == domain.Failed || processedOperation.State == domain.Succeeded { 141 logStep.Infof("Operation %q got status %s. Process finished.", operation.ID, processedOperation.State) 142 return 0, nil 143 } 144 145 // the step needs a retry 146 if when > 0 { 147 return when, nil 148 } 149 } 150 151 processedOperation, err = m.saveFinishedStage(processedOperation, stage, logOperation) 152 if err != nil { 153 return time.Second, nil 154 } 155 } 156 157 processedOperation.State = domain.Succeeded 158 processedOperation.Description = "update succeeded" 159 _, err = m.operationStorage.UpdateUpdatingOperation(processedOperation) 160 if err != nil { 161 logOperation.Infof("Unable to save operation with finished the provisioning process") 162 return time.Second, err 163 } 164 165 return 0, nil 166 } 167 168 func (m *Manager) saveFinishedStage(operation internal.UpdatingOperation, s *stage, log logrus.FieldLogger) (internal.UpdatingOperation, error) { 169 operation.FinishStage(s.name) 170 op, err := m.operationStorage.UpdateUpdatingOperation(operation) 171 if err != nil { 172 log.Infof("Unable to save operation with finished stage %s: %s", s.name, err.Error()) 173 return operation, err 174 } 175 log.Infof("Finished stage %s", s.name) 176 return *op, nil 177 } 178 179 func (m *Manager) runStep(step Step, operation internal.UpdatingOperation, logger logrus.FieldLogger) (internal.UpdatingOperation, time.Duration, error) { 180 begin := time.Now() 181 for { 182 start := time.Now() 183 processedOperation, when, err := step.Run(operation, logger) 184 m.publisher.Publish(context.TODO(), process.UpdatingStepProcessed{ 185 OldOperation: operation, 186 Operation: processedOperation, 187 StepProcessed: process.StepProcessed{ 188 StepName: step.Name(), 189 Duration: time.Since(start), 190 When: when, 191 Error: err, 192 }, 193 }) 194 195 // break the loop if: 196 // - the step does not need a retry 197 // - step returns an error 198 // - the loop takes too much time (to not block the worker too long) 199 if when == 0 || err != nil || time.Since(begin) > time.Minute { 200 return processedOperation, when, err 201 } 202 time.Sleep(when / time.Duration(m.speedFactor)) 203 } 204 } 205 206 func getComponent(componentProvider input.ComponentListProvider, component string, 207 kymaVersion internal.RuntimeVersionData, cfg *internal.ConfigForPlan) (*internal.KymaComponent, error) { 208 allComponents, err := componentProvider.AllComponents(kymaVersion, cfg) 209 if err != nil { 210 return nil, err 211 } 212 for _, c := range allComponents { 213 if c.Name == component { 214 return &c, nil 215 } 216 } 217 return nil, fmt.Errorf("failed to find %v component in all component list", component) 218 } 219 220 func getComponentInput(componentProvider input.ComponentListProvider, component string, 221 kymaVersion internal.RuntimeVersionData, cfg *internal.ConfigForPlan) (reconcilerApi.Component, error) { 222 c, err := getComponent(componentProvider, component, kymaVersion, cfg) 223 if err != nil { 224 return reconcilerApi.Component{}, err 225 } 226 return reconcilerApi.Component{ 227 Component: c.Name, 228 Namespace: c.Namespace, 229 URL: c.Source.URL, 230 }, nil 231 }