|
4 | 4 | "context" |
5 | 5 | "encoding/json" |
6 | 6 | "fmt" |
| 7 | + "slices" |
7 | 8 | "sort" |
8 | 9 | "strings" |
9 | 10 | "sync" |
@@ -416,6 +417,23 @@ func (sc *syncContext) setRunningPhase(tasks []*syncTask, isPendingDeletion bool |
416 | 417 | func (sc *syncContext) Sync() { |
417 | 418 | sc.log.WithValues("skipHooks", sc.skipHooks, "started", sc.started()).Info("Syncing") |
418 | 419 | tasks, ok := sc.getSyncTasks() |
| 420 | + |
| 421 | + // dependencyGraph will be used to detect circular dependencies and allow for direct dependencies definition |
| 422 | + // in argocd.argoproj.io/sync-wave-group-dependencies |
| 423 | + dependencyGraph := common.WaveDependencyGraph{Dependencies: make([]common.WaveDependency, 0)} |
| 424 | + for _, task := range tasks { |
| 425 | + if task.targetObj != nil { |
| 426 | + origin := common.GroupIdentity{Phase: task.phase, WaveGroup: task.wave()} |
| 427 | + for dependency := range task.waveGroupDependencies() { |
| 428 | + destination := common.GroupIdentity{Phase: task.phase, WaveGroup: dependency} |
| 429 | + waveDependency := &common.WaveDependency{Origin: origin, Destination: destination} |
| 430 | + if !slices.Contains(dependencyGraph.Dependencies, *waveDependency) { |
| 431 | + dependencyGraph.Dependencies = append(dependencyGraph.Dependencies, *waveDependency) |
| 432 | + } |
| 433 | + } |
| 434 | + } |
| 435 | + } |
| 436 | + |
419 | 437 | if !ok { |
420 | 438 | sc.setOperationPhase(common.OperationFailed, "one or more synchronization tasks are not valid") |
421 | 439 | return |
@@ -560,26 +578,29 @@ func (sc *syncContext) Sync() { |
560 | 578 | return |
561 | 579 | } |
562 | 580 |
|
563 | | - // remove any tasks not in this wave |
| 581 | + // remove any tasks which have unsynced dependencies |
564 | 582 | phase := tasks.phase() |
565 | | - wave := tasks.wave() |
566 | | - finalWave := phase == tasks.lastPhase() && wave == tasks.lastWave() |
| 583 | + independantSyncIdentities := tasks.independantSyncIdentities() |
| 584 | + allSyncIdentities := tasks.syncIdentities() |
567 | 585 |
|
568 | 586 | // if it is the last phase/wave and the only remaining tasks are non-hooks, the we are successful |
569 | 587 | // EVEN if those objects subsequently degraded |
570 | 588 | // This handles the common case where neither hooks or waves are used and a sync equates to simply an (asynchronous) kubectl apply of manifests, which succeeds immediately. |
571 | | - remainingTasks := tasks.Filter(func(t *syncTask) bool { return t.phase != phase || wave != t.wave() || t.isHook() }) |
| 589 | + remainingTasks := tasks.Filter(func(t *syncTask) bool { |
| 590 | + return !slices.Contains(independantSyncIdentities, t.identity()) || t.isHook() |
| 591 | + }) |
572 | 592 |
|
573 | | - sc.log.WithValues("phase", phase, "wave", wave, "tasks", tasks, "syncFailTasks", syncFailTasks).V(1).Info("Filtering tasks in correct phase and wave") |
574 | | - tasks = tasks.Filter(func(t *syncTask) bool { return t.phase == phase && t.wave() == wave }) |
| 593 | + sc.log.WithValues("phase", phase, "independantSyncIdentities", independantSyncIdentities, "tasks", tasks, "syncFailTasks", syncFailTasks).V(1).Info("Filtering tasks in correct phase and wave") |
| 594 | + tasks = tasks.Filter(func(t *syncTask) bool { return slices.Contains(independantSyncIdentities, t.identity()) }) |
575 | 595 |
|
576 | 596 | sc.setOperationPhase(common.OperationRunning, "one or more tasks are running") |
577 | 597 |
|
578 | 598 | sc.log.WithValues("tasks", tasks).V(1).Info("Wet-run") |
579 | 599 | runState := sc.runTasks(tasks, false) |
580 | 600 |
|
581 | 601 | if sc.syncWaveHook != nil && runState != failed { |
582 | | - err := sc.syncWaveHook(phase, wave, finalWave) |
| 602 | + finalWave := phase == tasks.lastPhase() && len(independantSyncIdentities) == len(allSyncIdentities) |
| 603 | + err := sc.syncWaveHook(independantSyncIdentities, finalWave) |
583 | 604 | if err != nil { |
584 | 605 | sc.deleteHooks(hooksPendingDeletionFailed) |
585 | 606 | sc.setOperationPhase(common.OperationFailed, fmt.Sprintf("SyncWaveHook failed: %v", err)) |
@@ -909,52 +930,61 @@ func (sc *syncContext) getSyncTasks() (_ syncTasks, successful bool) { |
909 | 930 | } |
910 | 931 |
|
911 | 932 | // for prune tasks, modify the waves for proper cleanup i.e reverse of sync wave (creation order) |
912 | | - pruneTasks := make(map[int][]*syncTask) |
| 933 | + |
| 934 | + tasksByWaveGroup := make(map[int][]*syncTask) |
913 | 935 | for _, task := range tasks { |
914 | | - if task.isPrune() { |
915 | | - pruneTasks[task.wave()] = append(pruneTasks[task.wave()], task) |
916 | | - } |
| 936 | + tasksByWaveGroup[task.waveGroup()] = append(tasksByWaveGroup[task.waveGroup()], task) |
917 | 937 | } |
| 938 | + for waveGroup := range tasksByWaveGroup { |
| 939 | + pruneTasks := make(map[int][]*syncTask) |
| 940 | + for _, task := range tasksByWaveGroup[waveGroup] { |
| 941 | + if task.isPrune() { |
| 942 | + pruneTasks[task.wave()] = append(pruneTasks[task.wave()], task) |
| 943 | + } |
| 944 | + } |
918 | 945 |
|
919 | | - var uniquePruneWaves []int |
920 | | - for k := range pruneTasks { |
921 | | - uniquePruneWaves = append(uniquePruneWaves, k) |
922 | | - } |
923 | | - sort.Ints(uniquePruneWaves) |
| 946 | + var uniquePruneWaves []int |
| 947 | + for k := range pruneTasks { |
| 948 | + uniquePruneWaves = append(uniquePruneWaves, k) |
| 949 | + } |
| 950 | + sort.Ints(uniquePruneWaves) |
924 | 951 |
|
925 | | - // reorder waves for pruning tasks using symmetric swap on prune waves |
926 | | - n := len(uniquePruneWaves) |
927 | | - for i := 0; i < n/2; i++ { |
928 | | - // waves to swap |
929 | | - startWave := uniquePruneWaves[i] |
930 | | - endWave := uniquePruneWaves[n-1-i] |
| 952 | + // reorder waves for pruning tasks using symmetric swap on prune waves |
| 953 | + n := len(uniquePruneWaves) |
| 954 | + for j := 0; j < n/2; j++ { |
| 955 | + // waves to swap |
| 956 | + startWave := uniquePruneWaves[j] |
| 957 | + endWave := uniquePruneWaves[n-1-j] |
931 | 958 |
|
932 | | - for _, task := range pruneTasks[startWave] { |
933 | | - task.waveOverride = &endWave |
934 | | - } |
| 959 | + for _, task := range pruneTasks[startWave] { |
| 960 | + task.waveOverride = &endWave |
| 961 | + } |
935 | 962 |
|
936 | | - for _, task := range pruneTasks[endWave] { |
937 | | - task.waveOverride = &startWave |
| 963 | + for _, task := range pruneTasks[endWave] { |
| 964 | + task.waveOverride = &startWave |
| 965 | + } |
938 | 966 | } |
939 | | - } |
940 | 967 |
|
941 | | - // for pruneLast tasks, modify the wave to sync phase last wave of tasks + 1 |
942 | | - // to ensure proper cleanup, syncPhaseLastWave should also consider prune tasks to determine last wave |
943 | | - syncPhaseLastWave := 0 |
944 | | - for _, task := range tasks { |
945 | | - if task.phase == common.SyncPhaseSync { |
946 | | - if task.wave() > syncPhaseLastWave { |
947 | | - syncPhaseLastWave = task.wave() |
| 968 | + // for pruneLast tasks, modify the wave to sync phase last wave of tasks + 1 |
| 969 | + // to ensure proper cleanup, syncPhaseLastWave should also consider prune tasks to determine last wave |
| 970 | + |
| 971 | + syncPhaseLastWave := 0 |
| 972 | + for _, task := range tasksByWaveGroup[waveGroup] { |
| 973 | + if task.phase == common.SyncPhaseSync { |
| 974 | + if task.wave() > syncPhaseLastWave { |
| 975 | + syncPhaseLastWave = task.wave() |
| 976 | + } |
948 | 977 | } |
949 | 978 | } |
950 | | - } |
951 | | - syncPhaseLastWave = syncPhaseLastWave + 1 |
| 979 | + syncPhaseLastWave = syncPhaseLastWave + 1 |
952 | 980 |
|
953 | | - for _, task := range tasks { |
954 | | - if task.isPrune() && |
955 | | - (sc.pruneLast || resourceutil.HasAnnotationOption(task.liveObj, common.AnnotationSyncOptions, common.SyncOptionPruneLast)) { |
956 | | - task.waveOverride = &syncPhaseLastWave |
| 981 | + for _, task := range tasksByWaveGroup[waveGroup] { |
| 982 | + if task.isPrune() && |
| 983 | + (sc.pruneLast || resourceutil.HasAnnotationOption(task.liveObj, common.AnnotationSyncOptions, common.SyncOptionPruneLast)) { |
| 984 | + task.waveOverride = &syncPhaseLastWave |
| 985 | + } |
957 | 986 | } |
| 987 | + |
958 | 988 | } |
959 | 989 |
|
960 | 990 | tasks.Sort() |
|
0 commit comments