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

     1  package manager
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	coreV1 "k8s.io/api/core/v1"
    12  	"k8s.io/apimachinery/pkg/util/wait"
    13  	"sigs.k8s.io/controller-runtime/pkg/client"
    14  
    15  	"github.com/kyma-project/kyma-environment-broker/common/orchestration"
    16  	"github.com/kyma-project/kyma-environment-broker/common/orchestration/strategies"
    17  	"github.com/kyma-project/kyma-environment-broker/internal"
    18  	kebError "github.com/kyma-project/kyma-environment-broker/internal/error"
    19  	"github.com/kyma-project/kyma-environment-broker/internal/notification"
    20  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    21  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    22  	"github.com/pivotal-cf/brokerapi/v8/domain"
    23  	"github.com/sirupsen/logrus"
    24  )
    25  
    26  type OperationFactory interface {
    27  	NewOperation(o internal.Orchestration, r orchestration.Runtime, i internal.Instance, state domain.LastOperationState) (orchestration.RuntimeOperation, error)
    28  	ResumeOperations(orchestrationID string) ([]orchestration.RuntimeOperation, error)
    29  	CancelOperation(orchestrationID string, runtimeID string) error
    30  	CancelOperations(orchestrationID string) error
    31  	RetryOperations(operationIDs []string) ([]orchestration.RuntimeOperation, error)
    32  	QueryOperation(orchestrationID string, r orchestration.Runtime) (bool, orchestration.RuntimeOperation, error)
    33  	QueryOperations(orchestrationID string) ([]orchestration.RuntimeOperation, error)
    34  	NotifyOperation(orchestrationID string, runtimeID string, oState string, notifyState orchestration.NotificationStateType) error
    35  }
    36  
    37  type orchestrationManager struct {
    38  	orchestrationStorage storage.Orchestrations
    39  	operationStorage     storage.Operations
    40  	instanceStorage      storage.Instances
    41  	resolver             orchestration.RuntimeResolver
    42  	factory              OperationFactory
    43  	executor             orchestration.OperationExecutor
    44  	log                  logrus.FieldLogger
    45  	pollingInterval      time.Duration
    46  	k8sClient            client.Client
    47  	configNamespace      string
    48  	configName           string
    49  	kymaVersion          string
    50  	kubernetesVersion    string
    51  	bundleBuilder        notification.BundleBuilder
    52  	speedFactor          int
    53  }
    54  
    55  const maintenancePolicyKeyName = "maintenancePolicy"
    56  const maintenanceWindowFormat = "150405-0700"
    57  
    58  func (m *orchestrationManager) SpeedUp(factor int) {
    59  	m.speedFactor = factor
    60  }
    61  
    62  func (m *orchestrationManager) Execute(orchestrationID string) (time.Duration, error) {
    63  	logger := m.log.WithField("orchestrationID", orchestrationID)
    64  	m.log.Infof("Processing orchestration %s", orchestrationID)
    65  	o, err := m.orchestrationStorage.GetByID(orchestrationID)
    66  	if err != nil {
    67  		if o == nil {
    68  			m.log.Errorf("orchestration %s failed: %s", orchestrationID, err)
    69  			return time.Minute, nil
    70  		}
    71  		return m.failOrchestration(o, fmt.Errorf("failed to get orchestration: %w", err))
    72  	}
    73  
    74  	operations, runtimeNums, err := m.waitForStart(o)
    75  	if err != nil {
    76  		m.failOrchestration(o, fmt.Errorf("failed while waiting start for operations: %w", err))
    77  	}
    78  
    79  	if o.Parameters.Kyma == nil || o.Parameters.Kyma.Version == "" {
    80  		o.Parameters.Kyma = &orchestration.KymaParameters{Version: m.kymaVersion}
    81  	}
    82  	if o.Parameters.Kubernetes == nil || o.Parameters.Kubernetes.KubernetesVersion == "" {
    83  		o.Parameters.Kubernetes = &orchestration.KubernetesParameters{KubernetesVersion: m.kubernetesVersion}
    84  	}
    85  
    86  	if o.State == orchestration.Pending || o.State == orchestration.Retrying {
    87  		if runtimeNums != 0 {
    88  			o.State = orchestration.InProgress
    89  		} else {
    90  			o.State = orchestration.Succeeded
    91  		}
    92  	}
    93  
    94  	err = m.orchestrationStorage.Update(*o)
    95  	if err != nil {
    96  		logger.Errorf("while updating orchestration: %v", err)
    97  		return m.pollingInterval, nil
    98  	}
    99  	// do not perform any action if the orchestration is finished
   100  	if o.IsFinished() {
   101  		m.log.Infof("Orchestration was already finished, state: %s", o.State)
   102  		return 0, nil
   103  	}
   104  
   105  	strategy := m.resolveStrategy(o.Parameters.Strategy.Type, m.executor, logger)
   106  
   107  	execID, err := strategy.Execute(operations, o.Parameters.Strategy)
   108  	if err != nil {
   109  		return 0, fmt.Errorf("failed to execute strategy: %w", err)
   110  	}
   111  
   112  	o, err = m.waitForCompletion(o, strategy, execID, logger)
   113  	if err != nil && kebError.IsTemporaryError(err) {
   114  		return 5 * time.Second, nil
   115  	} else if err != nil {
   116  		return 0, fmt.Errorf("while waiting for orchestration to finish: %w", err)
   117  	}
   118  
   119  	o.UpdatedAt = time.Now()
   120  	err = m.orchestrationStorage.Update(*o)
   121  	if err != nil {
   122  		logger.Errorf("while updating orchestration: %v", err)
   123  		return m.pollingInterval, nil
   124  	}
   125  
   126  	logger.Infof("Finished processing orchestration, state: %s", o.State)
   127  	return 0, nil
   128  }
   129  
   130  func (m *orchestrationManager) getMaintenancePolicy() (orchestration.MaintenancePolicy, error) {
   131  	policy := orchestration.MaintenancePolicy{}
   132  	config := &coreV1.ConfigMap{}
   133  	key := client.ObjectKey{Namespace: m.configNamespace, Name: m.configName}
   134  	if err := m.k8sClient.Get(context.Background(), key, config); err != nil {
   135  		return policy, fmt.Errorf("orchestration config is absent")
   136  	}
   137  
   138  	if config.Data[maintenancePolicyKeyName] == "" {
   139  		return policy, fmt.Errorf("maintenance policy is absent from orchestration config")
   140  	}
   141  
   142  	err := json.Unmarshal([]byte(config.Data[maintenancePolicyKeyName]), &policy)
   143  	if err != nil {
   144  		return policy, fmt.Errorf("failed to unmarshal the policy config")
   145  	}
   146  
   147  	return policy, nil
   148  }
   149  
   150  // result contains the operations which from `kcp o *** retry` and its label are retrying, runtimes from target parameter
   151  func (m *orchestrationManager) extractRuntimes(o *internal.Orchestration, runtimes []orchestration.Runtime, result []orchestration.RuntimeOperation) []orchestration.Runtime {
   152  	var fileterRuntimes []orchestration.Runtime
   153  	if o.State == orchestration.Pending {
   154  		fileterRuntimes = runtimes
   155  	} else {
   156  		// o.State = retrying / in progress
   157  		for _, retryOp := range result {
   158  			for _, r := range runtimes {
   159  				if retryOp.Runtime.InstanceID == r.InstanceID {
   160  					fileterRuntimes = append(fileterRuntimes, r)
   161  					break
   162  				}
   163  			}
   164  		}
   165  	}
   166  	return fileterRuntimes
   167  }
   168  
   169  func (m *orchestrationManager) NewOperationForPendingRetrying(o *internal.Orchestration, policy orchestration.MaintenancePolicy, retryRT []orchestration.RuntimeOperation, updateWindow bool) ([]orchestration.RuntimeOperation, *internal.Orchestration, []orchestration.Runtime, error) {
   170  	result := []orchestration.RuntimeOperation{}
   171  	runtimes, err := m.resolver.Resolve(o.Parameters.Targets)
   172  	if err != nil {
   173  		return result, o, runtimes, fmt.Errorf("while resolving targets: %w", err)
   174  	}
   175  
   176  	fileterRuntimes := m.extractRuntimes(o, runtimes, retryRT)
   177  
   178  	for _, r := range fileterRuntimes {
   179  		var op orchestration.RuntimeOperation
   180  		if o.State == orchestration.Pending {
   181  			exist, op, err := m.factory.QueryOperation(o.OrchestrationID, r)
   182  			if err != nil {
   183  				return nil, o, runtimes, fmt.Errorf("while quering operation for runtime id %q: %w", r.RuntimeID, err)
   184  			}
   185  			if exist {
   186  				result = append(result, op)
   187  				continue
   188  			}
   189  		}
   190  		if updateWindow {
   191  			windowBegin := time.Time{}
   192  			windowEnd := time.Time{}
   193  			days := []string{}
   194  
   195  			if o.State == orchestration.Pending && o.Parameters.Strategy.MaintenanceWindow {
   196  				windowBegin, windowEnd, days = resolveMaintenanceWindowTime(r, policy, o.Parameters.Strategy.ScheduleTime)
   197  			}
   198  			if o.State == orchestration.Retrying && bool(o.Parameters.RetryOperation.Immediate) && o.Parameters.Strategy.MaintenanceWindow {
   199  				windowBegin, windowEnd, days = resolveMaintenanceWindowTime(r, policy, o.Parameters.Strategy.ScheduleTime)
   200  			}
   201  
   202  			r.MaintenanceWindowBegin = windowBegin
   203  			r.MaintenanceWindowEnd = windowEnd
   204  			r.MaintenanceDays = days
   205  		} else {
   206  			if o.Parameters.RetryOperation.Immediate {
   207  				r.MaintenanceWindowBegin = time.Time{}
   208  				r.MaintenanceWindowEnd = time.Time{}
   209  				r.MaintenanceDays = []string{}
   210  			}
   211  		}
   212  
   213  		inst, err := m.instanceStorage.GetByID(r.InstanceID)
   214  		if err != nil {
   215  			return nil, o, runtimes, fmt.Errorf("while getting instance %s: %w", r.InstanceID, err)
   216  		}
   217  
   218  		op, err = m.factory.NewOperation(*o, r, *inst, orchestration.Pending)
   219  		if err != nil {
   220  			return nil, o, runtimes, fmt.Errorf("while creating new operation for runtime id %q: %w", r.RuntimeID, err)
   221  		}
   222  
   223  		result = append(result, op)
   224  
   225  	}
   226  
   227  	return result, o, fileterRuntimes, nil
   228  }
   229  
   230  func (m *orchestrationManager) cancelOperationForNonExistent(o *internal.Orchestration, resolvedOperations []orchestration.RuntimeOperation) error {
   231  	storageOperations, err := m.factory.QueryOperations(o.OrchestrationID)
   232  	if err != nil {
   233  		return fmt.Errorf("while quering operations for orchestration %s: %w", o.OrchestrationID, err)
   234  	}
   235  	var storageOpIDs []string
   236  	for _, storageOperation := range storageOperations {
   237  		storageOpIDs = append(storageOpIDs, storageOperation.Runtime.RuntimeID)
   238  	}
   239  	var resolvedOpIDs []string
   240  	for _, resolvedOperation := range resolvedOperations {
   241  		resolvedOpIDs = append(resolvedOpIDs, resolvedOperation.Runtime.RuntimeID)
   242  	}
   243  
   244  	//find diffs that exist in storageOperations but not in resolvedOperations
   245  	operationIdMap := make(map[string]struct{}, len(resolvedOpIDs))
   246  	for _, resolvedOpID := range resolvedOpIDs {
   247  		operationIdMap[resolvedOpID] = struct{}{}
   248  	}
   249  	var nonExistentIDs []string
   250  	for _, storageOpID := range storageOpIDs {
   251  		if _, found := operationIdMap[storageOpID]; !found {
   252  			nonExistentIDs = append(nonExistentIDs, storageOpID)
   253  		}
   254  	}
   255  
   256  	//cancel operations for non existent runtimes
   257  	for _, nonExistentID := range nonExistentIDs {
   258  		err := m.factory.CancelOperation(o.OrchestrationID, nonExistentID)
   259  		if err != nil {
   260  			return fmt.Errorf("while resolving canceled operations for runtime id %q: %w", nonExistentID, err)
   261  		}
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func (m *orchestrationManager) resolveOperations(o *internal.Orchestration, policy orchestration.MaintenancePolicy) ([]orchestration.RuntimeOperation, []orchestration.Runtime, error) {
   268  	result := []orchestration.RuntimeOperation{}
   269  	filterRuntimes := []orchestration.Runtime{}
   270  	if o.State == orchestration.Pending {
   271  		var err error
   272  		result, o, filterRuntimes, err = m.NewOperationForPendingRetrying(o, policy, result, true)
   273  		if err != nil {
   274  			return nil, filterRuntimes, fmt.Errorf("while creating new operation for pending: %w", err)
   275  		}
   276  		//cancel operations that no longer exist, and cancel their notification
   277  		err = m.cancelOperationForNonExistent(o, result)
   278  		if err != nil {
   279  			return nil, filterRuntimes, fmt.Errorf("while canceling non existent operation for pending: %w", err)
   280  		}
   281  
   282  		o.Description = fmt.Sprintf("Scheduled %d operations", len(filterRuntimes))
   283  	} else if o.State == orchestration.Retrying {
   284  		//check retry operation list, if empty return error
   285  		if len(o.Parameters.RetryOperation.RetryOperations) == 0 {
   286  			return nil, filterRuntimes, fmt.Errorf("while retrying operations: %w",
   287  				fmt.Errorf("o.Parameters.RetryOperation.RetryOperations is empty"))
   288  		}
   289  		retryRuntimes, err := m.factory.RetryOperations(o.Parameters.RetryOperation.RetryOperations)
   290  		if err != nil {
   291  			return retryRuntimes, filterRuntimes, fmt.Errorf("while resolving retrying orchestration: %w", err)
   292  		}
   293  
   294  		result, o, filterRuntimes, err = m.NewOperationForPendingRetrying(o, policy, retryRuntimes, true)
   295  
   296  		if err != nil {
   297  			return nil, filterRuntimes, fmt.Errorf("while NewOperationForPendingRetrying: %w", err)
   298  		}
   299  
   300  		o.Description = updateRetryingDescription(o.Description, fmt.Sprintf("retried %d operations", len(filterRuntimes)))
   301  		o.Parameters.RetryOperation.RetryOperations = nil
   302  		o.Parameters.RetryOperation.Immediate = false
   303  		m.log.Infof("Resuming %d operations for orchestration %s", len(result), o.OrchestrationID)
   304  	} else {
   305  		// Resume processing of not finished upgrade operations after restart
   306  		var err error
   307  		result, err = m.factory.ResumeOperations(o.OrchestrationID)
   308  		if err != nil {
   309  			return result, filterRuntimes, fmt.Errorf("while resuming operation: %w", err)
   310  		}
   311  
   312  		m.log.Infof("Resuming %d operations for orchestration %s", len(result), o.OrchestrationID)
   313  	}
   314  
   315  	return result, filterRuntimes, nil
   316  }
   317  
   318  func (m *orchestrationManager) resolveStrategy(sType orchestration.StrategyType, executor orchestration.OperationExecutor, log logrus.FieldLogger) orchestration.Strategy {
   319  	switch sType {
   320  	case orchestration.ParallelStrategy:
   321  		s := strategies.NewParallelOrchestrationStrategy(executor, log, 0)
   322  		if m.speedFactor != 0 {
   323  			s.SpeedUp(m.speedFactor)
   324  		}
   325  		return s
   326  	}
   327  	return nil
   328  }
   329  
   330  // waitForCompletion waits until processing of given orchestration ends or if it's canceled
   331  func (m *orchestrationManager) waitForCompletion(o *internal.Orchestration, strategy orchestration.Strategy, execID string, log logrus.FieldLogger) (*internal.Orchestration, error) {
   332  	orchestrationID := o.OrchestrationID
   333  	canceled := false
   334  	var err error
   335  	var stats map[string]int
   336  	execIDs := []string{execID}
   337  
   338  	err = wait.PollImmediateInfinite(m.pollingInterval, func() (bool, error) {
   339  		// check if orchestration wasn't canceled
   340  		o, err = m.orchestrationStorage.GetByID(orchestrationID)
   341  		switch {
   342  		case err == nil:
   343  			if o.State == orchestration.Canceling {
   344  				log.Info("Orchestration was canceled")
   345  				canceled = true
   346  			}
   347  		case dberr.IsNotFound(err):
   348  			log.Errorf("while getting orchestration: %v", err)
   349  			return false, err
   350  		default:
   351  			log.Errorf("while getting orchestration: %v", err)
   352  			return false, nil
   353  		}
   354  		s, err := m.operationStorage.GetOperationStatsForOrchestration(o.OrchestrationID)
   355  		if err != nil {
   356  			log.Errorf("while getting operations: %v", err)
   357  			return false, nil
   358  		}
   359  		stats = s
   360  
   361  		numberOfNotFinished := 0
   362  		numberOfInProgress, found := stats[orchestration.InProgress]
   363  		if found {
   364  			numberOfNotFinished += numberOfInProgress
   365  		}
   366  		numberOfPending, found := stats[orchestration.Pending]
   367  		if found {
   368  			numberOfNotFinished += numberOfPending
   369  		}
   370  		numberOfRetrying, found := stats[orchestration.Retrying]
   371  		if found {
   372  			numberOfNotFinished += numberOfRetrying
   373  		}
   374  
   375  		if len(o.Parameters.RetryOperation.RetryOperations) > 0 {
   376  			ops, err := m.factory.RetryOperations(o.Parameters.RetryOperation.RetryOperations)
   377  			if err != nil {
   378  				// don't block the polling and cancel signal
   379  				log.Errorf("PollImmediateInfinite() while handling retrying operations: %v", err)
   380  			}
   381  
   382  			result, o, _, err := m.NewOperationForPendingRetrying(o, orchestration.MaintenancePolicy{}, ops, false)
   383  			if err != nil {
   384  				log.Errorf("PollImmediateInfinite() while new operation for retrying instanceid : %v", err)
   385  			}
   386  
   387  			err = strategy.Insert(execID, result, o.Parameters.Strategy)
   388  			if err != nil {
   389  				retryExecID, err := strategy.Execute(result, o.Parameters.Strategy)
   390  				if err != nil {
   391  					return false, fmt.Errorf("while executing upgrade strategy during retrying: %w", err)
   392  				}
   393  				execIDs = append(execIDs, retryExecID)
   394  				execID = retryExecID
   395  			}
   396  			o.Description = updateRetryingDescription(o.Description, fmt.Sprintf("retried %d operations", len(o.Parameters.RetryOperation.RetryOperations)))
   397  			o.Parameters.RetryOperation.RetryOperations = nil
   398  			o.Parameters.RetryOperation.Immediate = false
   399  
   400  			err = m.orchestrationStorage.Update(*o)
   401  			if err != nil {
   402  				log.Errorf("PollImmediateInfinite() while updating orchestration: %v", err)
   403  				return false, nil
   404  			}
   405  			m.log.Infof("PollImmediateInfinite() while resuming %d operations for orchestration %s", len(result), o.OrchestrationID)
   406  		}
   407  
   408  		// don't wait for pending operations if orchestration was canceled
   409  		if canceled {
   410  			return numberOfInProgress == 0, nil
   411  		} else {
   412  			return numberOfNotFinished == 0, nil
   413  		}
   414  	})
   415  	if err != nil {
   416  		return nil, fmt.Errorf("while waiting for scheduled operations to finish: %w", err)
   417  	}
   418  
   419  	return m.resolveOrchestration(o, strategy, execIDs, stats)
   420  }
   421  
   422  func (m *orchestrationManager) resolveOrchestration(o *internal.Orchestration, strategy orchestration.Strategy, execIDs []string, stats map[string]int) (*internal.Orchestration, error) {
   423  	if o.State == orchestration.Canceling {
   424  		err := m.factory.CancelOperations(o.OrchestrationID)
   425  		if err != nil {
   426  			return nil, fmt.Errorf("while resolving canceled operations: %w", err)
   427  		}
   428  		for _, execID := range execIDs {
   429  			strategy.Cancel(execID)
   430  		}
   431  		// Send customer notification for cancel
   432  		if o.Parameters.Notification {
   433  			operations, err := m.factory.QueryOperations(o.OrchestrationID)
   434  			if err != nil {
   435  				return nil, fmt.Errorf("while quering operations for orchestration %s: %w", o.OrchestrationID, err)
   436  			}
   437  			err = m.sendNotificationCancel(o, operations)
   438  			//currently notification error can only be temporary error
   439  			if err != nil && kebError.IsTemporaryError(err) {
   440  				return nil, err
   441  			}
   442  			//update notification state for notified operations
   443  			for _, operation := range operations {
   444  				runtimeID := operation.Runtime.RuntimeID
   445  				err = m.factory.NotifyOperation(o.OrchestrationID, runtimeID, orchestration.Canceling, orchestration.NotificationCancelled)
   446  				if err != nil {
   447  					return nil, fmt.Errorf("while updaring operation for runtime id %q: %w", runtimeID, err)
   448  				}
   449  			}
   450  		}
   451  		o.State = orchestration.Canceled
   452  	} else {
   453  		state := orchestration.Succeeded
   454  		if stats[orchestration.Failed] > 0 {
   455  			state = orchestration.Failed
   456  		}
   457  		o.State = state
   458  	}
   459  	return o, nil
   460  }
   461  
   462  // resolves the next exact maintenance window time for the runtime
   463  func resolveMaintenanceWindowTime(r orchestration.Runtime, policy orchestration.MaintenancePolicy, after time.Time) (time.Time, time.Time, []string) {
   464  	ruleMatched := false
   465  
   466  	for _, p := range policy.Rules {
   467  		if p.Match.Plan != "" {
   468  			matched, err := regexp.MatchString(p.Match.Plan, r.Plan)
   469  			if err != nil || !matched {
   470  				continue
   471  			}
   472  		}
   473  
   474  		if p.Match.GlobalAccountID != "" {
   475  			matched, err := regexp.MatchString(p.Match.GlobalAccountID, r.GlobalAccountID)
   476  			if err != nil || !matched {
   477  				continue
   478  			}
   479  		}
   480  
   481  		if p.Match.Region != "" {
   482  			matched, err := regexp.MatchString(p.Match.Region, r.Region)
   483  			if err != nil || !matched {
   484  				continue
   485  			}
   486  		}
   487  
   488  		// We have a rule match here, either by one or all of the rule match options. Let's override maintenance attributes.
   489  		ruleMatched = true
   490  		if len(p.Days) > 0 {
   491  			r.MaintenanceDays = p.Days
   492  		}
   493  		if p.TimeBegin != "" {
   494  			if maintenanceWindowBegin, err := time.Parse(maintenanceWindowFormat, p.TimeBegin); err == nil {
   495  				r.MaintenanceWindowBegin = maintenanceWindowBegin
   496  			}
   497  		}
   498  		if p.TimeEnd != "" {
   499  			if maintenanceWindowEnd, err := time.Parse(maintenanceWindowFormat, p.TimeEnd); err == nil {
   500  				r.MaintenanceWindowEnd = maintenanceWindowEnd
   501  			}
   502  		}
   503  		break
   504  	}
   505  
   506  	// If non of the rules matched, try to apply the default rule
   507  	if !ruleMatched {
   508  		if len(policy.Default.Days) > 0 {
   509  			r.MaintenanceDays = policy.Default.Days
   510  		}
   511  		if policy.Default.TimeBegin != "" {
   512  			if maintenanceWindowBegin, err := time.Parse(maintenanceWindowFormat, policy.Default.TimeBegin); err == nil {
   513  				r.MaintenanceWindowBegin = maintenanceWindowBegin
   514  			}
   515  		}
   516  		if policy.Default.TimeEnd != "" {
   517  			if maintenanceWindowEnd, err := time.Parse(maintenanceWindowFormat, policy.Default.TimeEnd); err == nil {
   518  				r.MaintenanceWindowEnd = maintenanceWindowEnd
   519  			}
   520  		}
   521  	}
   522  
   523  	n := time.Now()
   524  	// If 'after' is in the future, set it as timepoint for the maintenance window calculation
   525  	if after.After(n) {
   526  		n = after
   527  	}
   528  	availableDays := orchestration.ConvertSliceOfDaysToMap(r.MaintenanceDays)
   529  	start := time.Date(n.Year(), n.Month(), n.Day(), r.MaintenanceWindowBegin.Hour(), r.MaintenanceWindowBegin.Minute(), r.MaintenanceWindowBegin.Second(), r.MaintenanceWindowBegin.Nanosecond(), r.MaintenanceWindowBegin.Location())
   530  	end := time.Date(n.Year(), n.Month(), n.Day(), r.MaintenanceWindowEnd.Hour(), r.MaintenanceWindowEnd.Minute(), r.MaintenanceWindowEnd.Second(), r.MaintenanceWindowEnd.Nanosecond(), r.MaintenanceWindowEnd.Location())
   531  	// Set start/end date to the first available day (including today)
   532  	diff := orchestration.FirstAvailableDayDiff(n.Weekday(), availableDays)
   533  	start = start.AddDate(0, 0, diff)
   534  	end = end.AddDate(0, 0, diff)
   535  
   536  	// if the window end slips through the next day, adjust the date accordingly
   537  	if end.Before(start) || end.Equal(start) {
   538  		end = end.AddDate(0, 0, 1)
   539  	}
   540  
   541  	// if time window has already passed we wait until next available day
   542  	if start.Before(n) && end.Before(n) {
   543  		diff := orchestration.NextAvailableDayDiff(n.Weekday(), availableDays)
   544  		start = start.AddDate(0, 0, diff)
   545  		end = end.AddDate(0, 0, diff)
   546  	}
   547  
   548  	return start, end, r.MaintenanceDays
   549  }
   550  
   551  func (m *orchestrationManager) failOrchestration(o *internal.Orchestration, err error) (time.Duration, error) {
   552  	m.log.Errorf("orchestration %s failed: %s", o.OrchestrationID, err)
   553  	return m.updateOrchestration(o, orchestration.Failed, err.Error()), nil
   554  }
   555  
   556  func (m *orchestrationManager) updateOrchestration(o *internal.Orchestration, state, description string) time.Duration {
   557  	o.UpdatedAt = time.Now()
   558  	o.State = state
   559  	o.Description = description
   560  	err := m.orchestrationStorage.Update(*o)
   561  	if err != nil {
   562  		if !dberr.IsNotFound(err) {
   563  			m.log.Errorf("while updating orchestration: %v", err)
   564  			return time.Minute
   565  		}
   566  	}
   567  	return 0
   568  }
   569  
   570  func (m *orchestrationManager) sendNotificationCreate(o *internal.Orchestration, operations []orchestration.RuntimeOperation) error {
   571  	eventType := ""
   572  	tenants := []notification.NotificationTenant{}
   573  	if o.Type == orchestration.UpgradeKymaOrchestration {
   574  		eventType = notification.KymaMaintenanceNumber
   575  	} else if o.Type == orchestration.UpgradeClusterOrchestration {
   576  		eventType = notification.KubernetesMaintenanceNumber
   577  	}
   578  
   579  	for _, operation := range operations {
   580  		startDate := ""
   581  		endDate := ""
   582  		if o.Parameters.Strategy.MaintenanceWindow {
   583  			startDate = operation.Runtime.MaintenanceWindowBegin.String()
   584  			endDate = operation.Runtime.MaintenanceWindowEnd.String()
   585  		} else {
   586  			startDate = time.Now().Format("2006-01-02 15:04:05")
   587  		}
   588  		tenant := notification.NotificationTenant{
   589  			InstanceID: operation.Runtime.InstanceID,
   590  			StartDate:  startDate,
   591  			EndDate:    endDate,
   592  		}
   593  		tenants = append(tenants, tenant)
   594  	}
   595  
   596  	notificationParams := notification.NotificationParams{
   597  		OrchestrationID: o.OrchestrationID,
   598  		EventType:       eventType,
   599  		Tenants:         tenants,
   600  	}
   601  	m.log.Info("Start to create notification")
   602  	notificationBundle, err := m.bundleBuilder.NewBundle(o.OrchestrationID, notificationParams)
   603  	if err != nil {
   604  		m.log.Errorf("%s: %s", "failed to create Notification Bundle", err)
   605  		return err
   606  	}
   607  	err = notificationBundle.CreateNotificationEvent()
   608  	if err != nil {
   609  		m.log.Errorf("%s: %s", "cannot send notification", err)
   610  		return err
   611  	}
   612  	m.log.Info("Creating notification succedded")
   613  
   614  	return nil
   615  }
   616  
   617  func (m *orchestrationManager) sendNotificationCancel(o *internal.Orchestration, ops []orchestration.RuntimeOperation) error {
   618  	eventType := ""
   619  	tenants := []notification.NotificationTenant{}
   620  	if o.Type == orchestration.UpgradeKymaOrchestration {
   621  		eventType = notification.KymaMaintenanceNumber
   622  	} else if o.Type == orchestration.UpgradeClusterOrchestration {
   623  		eventType = notification.KubernetesMaintenanceNumber
   624  	}
   625  	for _, op := range ops {
   626  		if op.NotificationState == orchestration.NotificationCreated {
   627  			tenant := notification.NotificationTenant{
   628  				InstanceID: op.Runtime.InstanceID,
   629  			}
   630  			tenants = append(tenants, tenant)
   631  		}
   632  	}
   633  	notificationParams := notification.NotificationParams{
   634  		OrchestrationID: o.OrchestrationID,
   635  		EventType:       eventType,
   636  		Tenants:         tenants,
   637  	}
   638  	m.log.Info("Start to cancel notification")
   639  	notificationBundle, err := m.bundleBuilder.NewBundle(o.OrchestrationID, notificationParams)
   640  	if err != nil {
   641  		m.log.Errorf("%s: %s", "failed to create Notification Bundle", err)
   642  		return err
   643  	}
   644  	err = notificationBundle.CancelNotificationEvent()
   645  	if err != nil {
   646  		m.log.Errorf("%s: %s", "cannot cancel notification", err)
   647  		return err
   648  	}
   649  	m.log.Info("Cancelling notification succedded")
   650  
   651  	return nil
   652  }
   653  
   654  func updateRetryingDescription(desc string, newDesc string) string {
   655  	if strings.Contains(desc, "retrying") {
   656  		return strings.Replace(desc, "retrying", newDesc, -1)
   657  	}
   658  
   659  	return desc + ", " + newDesc
   660  }
   661  
   662  func (m *orchestrationManager) waitForStart(o *internal.Orchestration) ([]orchestration.RuntimeOperation, int, error) {
   663  	maintenancePolicy, err := m.getMaintenancePolicy()
   664  	if err != nil {
   665  		m.log.Warnf("while getting maintenance policy: %s", err)
   666  	}
   667  
   668  	//polling every 5 min until ochestration start
   669  	pollingInterval := 5 * time.Minute
   670  	var operations, unnotified_operations []orchestration.RuntimeOperation
   671  	var filterRuntimes []orchestration.Runtime
   672  	err = wait.PollImmediateInfinite(pollingInterval, func() (bool, error) {
   673  		//resolve operations, cancel non existent ones
   674  		operations, filterRuntimes, err = m.resolveOperations(o, maintenancePolicy)
   675  		if err != nil {
   676  			return true, err
   677  		}
   678  
   679  		//send notification for each operation which doesn't have one
   680  		if o.Parameters.Notification && o.State == orchestration.Pending {
   681  			for _, operation := range operations {
   682  				if operation.NotificationState == "" {
   683  					unnotified_operations = append(unnotified_operations, operation)
   684  				}
   685  			}
   686  			err = m.sendNotificationCreate(o, unnotified_operations)
   687  			//currently notification error can only be temporary error
   688  			if err != nil && kebError.IsTemporaryError(err) {
   689  				return true, err
   690  			}
   691  
   692  			//update notification state for notified operations
   693  			for _, operation := range unnotified_operations {
   694  				runtimeID := operation.Runtime.RuntimeID
   695  				err = m.factory.NotifyOperation(o.OrchestrationID, runtimeID, orchestration.Pending, orchestration.NotificationCreated)
   696  				if err != nil {
   697  					return true, fmt.Errorf("while updaring operation for runtime id %q: %w", runtimeID, err)
   698  				}
   699  			}
   700  		}
   701  
   702  		//leave polling when ochestration starts
   703  		if time.Now().After(o.Parameters.Strategy.ScheduleTime) {
   704  			return true, nil
   705  		}
   706  		return false, nil
   707  	})
   708  	if err != nil {
   709  		return []orchestration.RuntimeOperation{}, len(filterRuntimes), fmt.Errorf("while waiting for orchestration start: %w", err)
   710  	}
   711  	return operations, len(filterRuntimes), nil
   712  }