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 }