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  }