From 9bd180bd4c3e35873e83b02294e736f92637fb16 Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Tue, 16 Apr 2024 15:23:15 +0200 Subject: [PATCH 1/6] Add UpdateStrategy field --- api/v1/ytsaurus_types.go | 16 ++++++++++++++++ .../bases/cluster.ytsaurus.tech_ytsaurus.yaml | 6 ++++++ docs/api.md | 16 ++++++++++++++++ ytop-chart/templates/ytsaurus-crd.yaml | 6 ++++++ 4 files changed, 44 insertions(+) diff --git a/api/v1/ytsaurus_types.go b/api/v1/ytsaurus_types.go index 11a48684..fc32a1d6 100644 --- a/api/v1/ytsaurus_types.go +++ b/api/v1/ytsaurus_types.go @@ -546,6 +546,10 @@ type YtsaurusSpec struct { //+kubebuilder:default:=true //+optional EnableFullUpdate bool `json:"enableFullUpdate"` + //+optional + // UpdateStrategy is an experimental field. Behaviour may change. + // If UpdateStrategy is not empty EnableFullUpdate is ignored. + UpdateStrategy UpdateStrategy `json:"updateStrategy"` Bootstrap *BootstrapSpec `json:"bootstrap,omitempty"` @@ -618,10 +622,22 @@ type TabletCellBundleInfo struct { TabletCellCount int `yson:"tablet_cell_count,attr" json:"tabletCellCount"` } +type UpdateStrategy string + +const ( + UpdateStrategyNone UpdateStrategy = "" + UpdateStrategyBlocked UpdateStrategy = "Blocked" + UpdateStrategyStatelessOnly UpdateStrategy = "StatelessOnly" + UpdateStrategyMasterOnly UpdateStrategy = "MasterOnly" + UpdateStrategyTabletNodesOnly UpdateStrategy = "TabletNodesOnly" + UpdateStrategyFull UpdateStrategy = "Full" +) + type UpdateStatus struct { //+kubebuilder:default:=None State UpdateState `json:"state,omitempty"` Components []string `json:"components,omitempty"` + Strategy UpdateStrategy `json:"updateStrategy,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` TabletCellBundles []TabletCellBundleInfo `json:"tabletCellBundles,omitempty"` MasterMonitoringPaths []string `json:"masterMonitoringPaths,omitempty"` diff --git a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml index 67dff921..6c0a580e 100644 --- a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml +++ b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml @@ -33433,6 +33433,10 @@ spec: type: object uiImage: type: string + updateStrategy: + description: UpdateStrategy is an experimental field. Behaviour may + change. + type: string useIpv4: default: false type: boolean @@ -35902,6 +35906,8 @@ spec: - tabletCellCount type: object type: array + updateStrategy: + type: string type: object type: object type: object diff --git a/docs/api.md b/docs/api.md index a3f8116a..a0b6a400 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1489,11 +1489,26 @@ _Appears in:_ | --- | --- | --- | --- | | `state` _[UpdateState](#updatestate)_ | | None | | | `components` _string array_ | | | | +| `updateStrategy` _[UpdateStrategy](#updatestrategy)_ | | | | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ | | | | | `tabletCellBundles` _[TabletCellBundleInfo](#tabletcellbundleinfo) array_ | | | | | `masterMonitoringPaths` _string array_ | | | | +#### UpdateStrategy + +_Underlying type:_ _string_ + + + + + +_Appears in:_ +- [UpdateStatus](#updatestatus) +- [YtsaurusSpec](#ytsaurusspec) + + + #### YQLAgentSpec @@ -1577,6 +1592,7 @@ _Appears in:_ | `oauthService` _[OauthServiceSpec](#oauthservicespec)_ | | | | | `isManaged` _boolean_ | | true | | | `enableFullUpdate` _boolean_ | | true | | +| `updateStrategy` _[UpdateStrategy](#updatestrategy)_ | UpdateStrategy is an experimental field. Behaviour may change.
If UpdateStrategy is not empty EnableFullUpdate is ignored. | | | | `bootstrap` _[BootstrapSpec](#bootstrapspec)_ | | | | | `discovery` _[DiscoverySpec](#discoveryspec)_ | | | | | `primaryMasters` _[MastersSpec](#mastersspec)_ | | | | diff --git a/ytop-chart/templates/ytsaurus-crd.yaml b/ytop-chart/templates/ytsaurus-crd.yaml index a63cada9..d4f0939f 100644 --- a/ytop-chart/templates/ytsaurus-crd.yaml +++ b/ytop-chart/templates/ytsaurus-crd.yaml @@ -33220,6 +33220,10 @@ spec: type: object uiImage: type: string + updateStrategy: + description: UpdateStrategy is an experimental field. Behaviour may + change. + type: string useIpv4: default: false type: boolean @@ -35670,6 +35674,8 @@ spec: - tabletCellCount type: object type: array + updateStrategy: + type: string type: object type: object type: object From ddb402df44f5274e0e5d19d5ed0b45e81f6a273f Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Thu, 18 Apr 2024 17:09:24 +0200 Subject: [PATCH 2/6] Strategy = selector + flow --- api/v1/ytsaurus_types.go | 33 ++++++++++----- .../bases/cluster.ytsaurus.tech_ytsaurus.yaml | 8 ++-- docs/api.md | 42 ++++++++++++------- ytop-chart/templates/ytsaurus-crd.yaml | 8 ++-- 4 files changed, 57 insertions(+), 34 deletions(-) diff --git a/api/v1/ytsaurus_types.go b/api/v1/ytsaurus_types.go index fc32a1d6..6e0effe0 100644 --- a/api/v1/ytsaurus_types.go +++ b/api/v1/ytsaurus_types.go @@ -547,9 +547,9 @@ type YtsaurusSpec struct { //+optional EnableFullUpdate bool `json:"enableFullUpdate"` //+optional - // UpdateStrategy is an experimental field. Behaviour may change. - // If UpdateStrategy is not empty EnableFullUpdate is ignored. - UpdateStrategy UpdateStrategy `json:"updateStrategy"` + // UpdateSelector is an experimental field. Behaviour may change. + // If UpdateSelector is not empty EnableFullUpdate is ignored. + UpdateSelector UpdateSelector `json:"updateSelector"` Bootstrap *BootstrapSpec `json:"bootstrap,omitempty"` @@ -622,22 +622,33 @@ type TabletCellBundleInfo struct { TabletCellCount int `yson:"tablet_cell_count,attr" json:"tabletCellCount"` } -type UpdateStrategy string +type UpdateSelector string const ( - UpdateStrategyNone UpdateStrategy = "" - UpdateStrategyBlocked UpdateStrategy = "Blocked" - UpdateStrategyStatelessOnly UpdateStrategy = "StatelessOnly" - UpdateStrategyMasterOnly UpdateStrategy = "MasterOnly" - UpdateStrategyTabletNodesOnly UpdateStrategy = "TabletNodesOnly" - UpdateStrategyFull UpdateStrategy = "Full" + UpdateSelectorNone UpdateSelector = "" + UpdateSelectorNothing UpdateSelector = "Nothing" + UpdateSelectorStatelessOnly UpdateSelector = "StatelessOnly" + UpdateSelectorMasterOnly UpdateSelector = "MasterOnly" + UpdateSelectorTabletNodesOnly UpdateSelector = "TabletNodesOnly" + UpdateSelectorExecNodesOnly UpdateSelector = "ExecNodesOnly" + UpdateSelectorEverything UpdateSelector = "Everything" +) + +type UpdateFlow string + +const ( + UpdateFlowNone UpdateFlow = "" + UpdateFlowStateless UpdateFlow = "Stateless" + UpdateFlowMaster UpdateFlow = "Master" + UpdateFlowTabletNodes UpdateFlow = "TabletNodes" + UpdateFlowFull UpdateFlow = "Full" ) type UpdateStatus struct { //+kubebuilder:default:=None State UpdateState `json:"state,omitempty"` Components []string `json:"components,omitempty"` - Strategy UpdateStrategy `json:"updateStrategy,omitempty"` + Flow UpdateFlow `json:"flow,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` TabletCellBundles []TabletCellBundleInfo `json:"tabletCellBundles,omitempty"` MasterMonitoringPaths []string `json:"masterMonitoringPaths,omitempty"` diff --git a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml index 6c0a580e..40010734 100644 --- a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml +++ b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml @@ -33433,8 +33433,8 @@ spec: type: object uiImage: type: string - updateStrategy: - description: UpdateStrategy is an experimental field. Behaviour may + updateSelector: + description: UpdateSelector is an experimental field. Behaviour may change. type: string useIpv4: @@ -35887,6 +35887,8 @@ spec: - type type: object type: array + flow: + type: string masterMonitoringPaths: items: type: string @@ -35906,8 +35908,6 @@ spec: - tabletCellCount type: object type: array - updateStrategy: - type: string type: object type: object type: object diff --git a/docs/api.md b/docs/api.md index a0b6a400..1d337965 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1461,7 +1461,7 @@ _Appears in:_ | `group` _string_ | | | | -#### UpdateState +#### UpdateFlow _Underlying type:_ _string_ @@ -1474,28 +1474,20 @@ _Appears in:_ -#### UpdateStatus - +#### UpdateSelector +_Underlying type:_ _string_ _Appears in:_ -- [YtsaurusStatus](#ytsaurusstatus) +- [YtsaurusSpec](#ytsaurusspec) -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `state` _[UpdateState](#updatestate)_ | | None | | -| `components` _string array_ | | | | -| `updateStrategy` _[UpdateStrategy](#updatestrategy)_ | | | | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ | | | | -| `tabletCellBundles` _[TabletCellBundleInfo](#tabletcellbundleinfo) array_ | | | | -| `masterMonitoringPaths` _string array_ | | | | -#### UpdateStrategy +#### UpdateState _Underlying type:_ _string_ @@ -1505,10 +1497,30 @@ _Underlying type:_ _string_ _Appears in:_ - [UpdateStatus](#updatestatus) -- [YtsaurusSpec](#ytsaurusspec) +#### UpdateStatus + + + + + + + +_Appears in:_ +- [YtsaurusStatus](#ytsaurusstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `state` _[UpdateState](#updatestate)_ | | None | | +| `components` _string array_ | | | | +| `flow` _[UpdateFlow](#updateflow)_ | | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ | | | | +| `tabletCellBundles` _[TabletCellBundleInfo](#tabletcellbundleinfo) array_ | | | | +| `masterMonitoringPaths` _string array_ | | | | + + #### YQLAgentSpec @@ -1592,7 +1604,7 @@ _Appears in:_ | `oauthService` _[OauthServiceSpec](#oauthservicespec)_ | | | | | `isManaged` _boolean_ | | true | | | `enableFullUpdate` _boolean_ | | true | | -| `updateStrategy` _[UpdateStrategy](#updatestrategy)_ | UpdateStrategy is an experimental field. Behaviour may change.
If UpdateStrategy is not empty EnableFullUpdate is ignored. | | | +| `updateSelector` _[UpdateSelector](#updateselector)_ | UpdateSelector is an experimental field. Behaviour may change.
If UpdateSelector is not empty EnableFullUpdate is ignored. | | | | `bootstrap` _[BootstrapSpec](#bootstrapspec)_ | | | | | `discovery` _[DiscoverySpec](#discoveryspec)_ | | | | | `primaryMasters` _[MastersSpec](#mastersspec)_ | | | | diff --git a/ytop-chart/templates/ytsaurus-crd.yaml b/ytop-chart/templates/ytsaurus-crd.yaml index d4f0939f..e863a0b1 100644 --- a/ytop-chart/templates/ytsaurus-crd.yaml +++ b/ytop-chart/templates/ytsaurus-crd.yaml @@ -33220,8 +33220,8 @@ spec: type: object uiImage: type: string - updateStrategy: - description: UpdateStrategy is an experimental field. Behaviour may + updateSelector: + description: UpdateSelector is an experimental field. Behaviour may change. type: string useIpv4: @@ -35655,6 +35655,8 @@ spec: - type type: object type: array + flow: + type: string masterMonitoringPaths: items: type: string @@ -35674,8 +35676,6 @@ spec: - tabletCellCount type: object type: array - updateStrategy: - type: string type: object type: object type: object From 1a0c5b6cfe56f5ea381227c7fed105b9a1b6bc40 Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Mon, 22 Apr 2024 10:34:25 +0200 Subject: [PATCH 3/6] Unspecified + flow comment --- api/v1/ytsaurus_types.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/api/v1/ytsaurus_types.go b/api/v1/ytsaurus_types.go index 6e0effe0..93df16cd 100644 --- a/api/v1/ytsaurus_types.go +++ b/api/v1/ytsaurus_types.go @@ -625,13 +625,22 @@ type TabletCellBundleInfo struct { type UpdateSelector string const ( - UpdateSelectorNone UpdateSelector = "" - UpdateSelectorNothing UpdateSelector = "Nothing" - UpdateSelectorStatelessOnly UpdateSelector = "StatelessOnly" - UpdateSelectorMasterOnly UpdateSelector = "MasterOnly" + // UpdateSelectorUnspecified means that selector is disabled and would be ignored completely. + UpdateSelectorUnspecified UpdateSelector = "" + // UpdateSelectorNothing means that no component could be updated. + UpdateSelectorNothing UpdateSelector = "Nothing" + // UpdateSelectorStatelessOnly means that only stateless components (everything but master and tablet nodes) + // could be updated. + UpdateSelectorStatelessOnly UpdateSelector = "StatelessOnly" + // UpdateSelectorMasterOnly means that only master could be updated. + UpdateSelectorMasterOnly UpdateSelector = "MasterOnly" + // UpdateSelectorTabletNodesOnly means that only tablet nodes could be updated UpdateSelectorTabletNodesOnly UpdateSelector = "TabletNodesOnly" - UpdateSelectorExecNodesOnly UpdateSelector = "ExecNodesOnly" - UpdateSelectorEverything UpdateSelector = "Everything" + // UpdateSelectorExecNodesOnly means that only tablet nodes could be updated + UpdateSelectorExecNodesOnly UpdateSelector = "ExecNodesOnly" + // UpdateSelectorEverything means that all components could be updated. + // With this setting and if master or tablet nodes need update all the components would be updated. + UpdateSelectorEverything UpdateSelector = "Everything" ) type UpdateFlow string @@ -646,8 +655,11 @@ const ( type UpdateStatus struct { //+kubebuilder:default:=None - State UpdateState `json:"state,omitempty"` - Components []string `json:"components,omitempty"` + State UpdateState `json:"state,omitempty"` + Components []string `json:"components,omitempty"` + // Flow is an internal field that is needed to persist the chosen flow until the end of an update. + // Flow can be on of ""(unspecified), Stateless, Master, TabletNodes, Full and update cluster stage + // executes steps corresponding to that update flow. Flow UpdateFlow `json:"flow,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` TabletCellBundles []TabletCellBundleInfo `json:"tabletCellBundles,omitempty"` From 0802d2c79d19861e792d0b7bf51e08ef491f52e4 Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Mon, 22 Apr 2024 10:37:01 +0200 Subject: [PATCH 4/6] regenerate --- config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml | 2 ++ docs/api.md | 2 +- ytop-chart/templates/ytsaurus-crd.yaml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml index 40010734..5b4cc373 100644 --- a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml +++ b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml @@ -35888,6 +35888,8 @@ spec: type: object type: array flow: + description: Flow is an internal field that is needed to persist + the chosen flow until the en type: string masterMonitoringPaths: items: diff --git a/docs/api.md b/docs/api.md index 1d337965..b94e5f06 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1515,7 +1515,7 @@ _Appears in:_ | --- | --- | --- | --- | | `state` _[UpdateState](#updatestate)_ | | None | | | `components` _string array_ | | | | -| `flow` _[UpdateFlow](#updateflow)_ | | | | +| `flow` _[UpdateFlow](#updateflow)_ | Flow is an internal field that is needed to persist the chosen flow until the end of an update.
Flow can be on of ""(unspecified), Stateless, Master, TabletNodes, Full and update cluster stage
executes steps corresponding to that update flow. | | | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ | | | | | `tabletCellBundles` _[TabletCellBundleInfo](#tabletcellbundleinfo) array_ | | | | | `masterMonitoringPaths` _string array_ | | | | diff --git a/ytop-chart/templates/ytsaurus-crd.yaml b/ytop-chart/templates/ytsaurus-crd.yaml index e863a0b1..d3731c76 100644 --- a/ytop-chart/templates/ytsaurus-crd.yaml +++ b/ytop-chart/templates/ytsaurus-crd.yaml @@ -35656,6 +35656,8 @@ spec: type: object type: array flow: + description: Flow is an internal field that is needed to persist + the chosen flow until the en type: string masterMonitoringPaths: items: From 8bd0a15fd3d586c7e6c09e0490a3ce9ebad663e4 Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Thu, 18 Apr 2024 17:12:04 +0200 Subject: [PATCH 5/6] Selectors impl --- controllers/sync.go | 287 +++++++++++++++++++++++++++--- pkg/apiproxy/ytsaurus.go | 8 +- pkg/components/ytsaurus_client.go | 4 - 3 files changed, 272 insertions(+), 27 deletions(-) diff --git a/controllers/sync.go b/controllers/sync.go index 00129eed..fbd506bb 100644 --- a/controllers/sync.go +++ b/controllers/sync.go @@ -2,6 +2,7 @@ package controllers import ( "context" + "fmt" "time" "github.com/ytsaurus/yt-k8s-operator/pkg/components" @@ -14,7 +15,7 @@ import ( apiProxy "github.com/ytsaurus/yt-k8s-operator/pkg/apiproxy" ) -func (r *YtsaurusReconciler) handleUpdatingStateFullMode( +func (r *YtsaurusReconciler) handleEverything( ctx context.Context, ytsaurus *apiProxy.Ytsaurus, componentManager *ComponentManager, @@ -160,7 +161,7 @@ func (r *YtsaurusReconciler) handleUpdatingStateFullMode( return nil, nil } -func (r *YtsaurusReconciler) handleUpdatingStateLocalMode( +func (r *YtsaurusReconciler) handleStateless( ctx context.Context, ytsaurus *apiProxy.Ytsaurus, componentManager *ComponentManager, @@ -232,6 +233,159 @@ func (r *YtsaurusReconciler) handleUpdatingStateLocalMode( return nil, nil } +func (r *YtsaurusReconciler) handleMasterOnly( + ctx context.Context, + ytsaurus *apiProxy.Ytsaurus, + componentManager *ComponentManager, +) (*ctrl.Result, error) { + resource := ytsaurus.GetResource() + + switch resource.Status.UpdateStatus.State { + case ytv1.UpdateStateNone: + ytsaurus.LogUpdate(ctx, "Checking the possibility of updating") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStatePossibilityCheck) + return &ctrl.Result{Requeue: true}, err + + case ytv1.UpdateStatePossibilityCheck: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionHasPossibility) { + ytsaurus.LogUpdate(ctx, "Waiting for safe mode enabled") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForSafeModeEnabled) + return &ctrl.Result{Requeue: true}, err + } else if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionNoPossibility) { + ytsaurus.LogUpdate(ctx, "Update is impossible, need to apply previous images") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateImpossibleToStart) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateImpossibleToStart: + if !componentManager.needSync() || !ytsaurus.GetResource().Spec.EnableFullUpdate { + ytsaurus.LogUpdate(ctx, "Spec changed back or full update isn't enabled, update is canceling") + err := ytsaurus.SaveClusterState(ctx, ytv1.ClusterStateCancelUpdate) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForSafeModeEnabled: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionSafeModeEnabled) { + ytsaurus.LogUpdate(ctx, "Waiting for tablet cells saving") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForSnapshots) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForSnapshots: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionSnaphotsSaved) { + ytsaurus.LogUpdate(ctx, "Waiting for pods removal") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForPodsRemoval) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForPodsRemoval: + if componentManager.arePodsRemoved() { + ytsaurus.LogUpdate(ctx, "Waiting for pods creation") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForPodsCreation) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForPodsCreation: + if componentManager.allReadyOrUpdating() { + ytsaurus.LogUpdate(ctx, "All components were recreated") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForMasterExitReadOnly) + return &ctrl.Result{RequeueAfter: time.Second * 7}, err + } + + case ytv1.UpdateStateWaitingForMasterExitReadOnly: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionMasterExitedReadOnly) { + ytsaurus.LogUpdate(ctx, "Masters exited read-only state") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForSafeModeDisabled) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForSafeModeDisabled: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionSafeModeDisabled) { + ytsaurus.LogUpdate(ctx, "Finishing") + err := ytsaurus.SaveClusterState(ctx, ytv1.ClusterStateUpdateFinishing) + return &ctrl.Result{Requeue: true}, err + } + } + return nil, nil +} + +func (r *YtsaurusReconciler) handleTabletNodesOnly( + ctx context.Context, + ytsaurus *apiProxy.Ytsaurus, + componentManager *ComponentManager, +) (*ctrl.Result, error) { + resource := ytsaurus.GetResource() + + switch resource.Status.UpdateStatus.State { + case ytv1.UpdateStateNone: + ytsaurus.LogUpdate(ctx, "Checking the possibility of updating") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStatePossibilityCheck) + return &ctrl.Result{Requeue: true}, err + + case ytv1.UpdateStatePossibilityCheck: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionHasPossibility) { + ytsaurus.LogUpdate(ctx, "Waiting for safe mode enabled") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForTabletCellsSaving) + return &ctrl.Result{Requeue: true}, err + } else if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionNoPossibility) { + ytsaurus.LogUpdate(ctx, "Update is impossible, need to apply previous images") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateImpossibleToStart) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateImpossibleToStart: + if !componentManager.needSync() || !ytsaurus.GetResource().Spec.EnableFullUpdate { + ytsaurus.LogUpdate(ctx, "Spec changed back or full update isn't enabled, update is canceling") + err := ytsaurus.SaveClusterState(ctx, ytv1.ClusterStateCancelUpdate) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForTabletCellsSaving: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionTabletCellsSaved) { + ytsaurus.LogUpdate(ctx, "Waiting for tablet cells removing to start") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForTabletCellsRemovingStart) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForTabletCellsRemovingStart: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionTabletCellsRemovingStarted) { + ytsaurus.LogUpdate(ctx, "Waiting for tablet cells removing to finish") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForTabletCellsRemoved) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForTabletCellsRemoved: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionTabletCellsRemoved) { + ytsaurus.LogUpdate(ctx, "Waiting for snapshots") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForPodsRemoval) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForPodsRemoval: + if componentManager.arePodsRemoved() { + ytsaurus.LogUpdate(ctx, "Waiting for pods creation") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForPodsCreation) + return &ctrl.Result{Requeue: true}, err + } + + case ytv1.UpdateStateWaitingForPodsCreation: + if componentManager.allReadyOrUpdating() { + ytsaurus.LogUpdate(ctx, "All components were recreated") + err := ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForTabletCellsRecovery) + return &ctrl.Result{RequeueAfter: time.Second * 7}, err + } + + case ytv1.UpdateStateWaitingForTabletCellsRecovery: + if ytsaurus.IsUpdateStatusConditionTrue(consts.ConditionTabletCellsRecovered) { + ytsaurus.LogUpdate(ctx, "Finishing") + err := ytsaurus.SaveClusterState(ctx, ytv1.ClusterStateUpdateFinishing) + return &ctrl.Result{Requeue: true}, err + } + } + + return nil, nil +} + func getComponentNames(components []components.Component) []string { if components == nil { return nil @@ -243,35 +397,112 @@ func getComponentNames(components []components.Component) []string { return names } -// chooseUpdateStrategy considers spec decides if operator should proceed with update or block. -// Block is indicated with non-empty blockMsg. -// Component names which are chosen for update are return in names slice. -// Nil names slice means "full update". -func chooseUpdateStrategy(spec ytv1.YtsaurusSpec, needUpdate []components.Component) (names []string, blockMsg string) { +type updateMeta struct { + flow ytv1.UpdateFlow + // componentNames is a list of component names that will be updated. It is built according to the update selector. + componentNames []string +} + +// chooseUpdateFlow considers spec and decides if operator should proceed with update or block. +// Block case is indicated with non-empty blockMsg. +// If update is not blocked, updateMeta containing a chosen flow and the component names to update returned. +func chooseUpdateFlow(spec ytv1.YtsaurusSpec, needUpdate []components.Component) (meta updateMeta, blockMsg string) { isFullUpdateEnabled := spec.EnableFullUpdate + configuredSelector := spec.UpdateSelector masterNeedsUpdate := false tabletNodesNeedUpdate := false + execNodesNeedUpdate := false + statelessNeedUpdate := false + var masterNames []string + var tabletNodeNames []string + var execNodeNames []string + var statelessNames []string for _, comp := range needUpdate { if comp.GetType() == consts.MasterType { masterNeedsUpdate = true + masterNames = append(masterNames, comp.GetName()) continue } if comp.GetType() == consts.TabletNodeType { tabletNodesNeedUpdate = true + tabletNodeNames = append(tabletNodeNames, comp.GetName()) continue } + if comp.GetType() == consts.ExecNodeType { + execNodesNeedUpdate = true + execNodeNames = append(execNodeNames, comp.GetName()) + } + statelessNames = append(statelessNames, comp.GetName()) + statelessNeedUpdate = true } statefulNeedUpdate := masterNeedsUpdate || tabletNodesNeedUpdate - if statefulNeedUpdate { - if isFullUpdateEnabled { - return nil, "" - } else { - return nil, "Full update is not allowed by enableFullUpdate field, ignoring it" + allNamesNeedingUpdate := getComponentNames(needUpdate) + + // Fallback to EnableFullUpdate field. + if configuredSelector == ytv1.UpdateSelectorNone { + if statefulNeedUpdate { + if isFullUpdateEnabled { + return updateMeta{flow: ytv1.UpdateFlowFull, componentNames: nil}, "" + } else { + return updateMeta{flow: "", componentNames: nil}, "Full update is not allowed by enableFullUpdate field, ignoring it" + } } + return updateMeta{flow: ytv1.UpdateFlowStateless, componentNames: allNamesNeedingUpdate}, "" + } + + switch configuredSelector { + case ytv1.UpdateSelectorNothing: + return updateMeta{}, "All updates are blocked by updateSelector field." + case ytv1.UpdateSelectorEverything: + if statefulNeedUpdate { + return updateMeta{ + flow: ytv1.UpdateFlowFull, + componentNames: nil, + }, "" + } else { + return updateMeta{ + flow: ytv1.UpdateFlowStateless, + componentNames: allNamesNeedingUpdate, + }, "" + } + case ytv1.UpdateSelectorMasterOnly: + if !masterNeedsUpdate { + return updateMeta{}, "Only Master update is allowed by updateSelector, but it doesn't need update" + } + return updateMeta{ + flow: ytv1.UpdateFlowMaster, + componentNames: masterNames, + }, "" + case ytv1.UpdateSelectorTabletNodesOnly: + if !tabletNodesNeedUpdate { + return updateMeta{}, "Only Tablet nodes update is allowed by updateSelector, but they don't need update" + } + return updateMeta{ + flow: ytv1.UpdateFlowTabletNodes, + componentNames: tabletNodeNames, + }, "" + case ytv1.UpdateSelectorExecNodesOnly: + if !execNodesNeedUpdate { + return updateMeta{}, "Only Exec nodes update is allowed by updateSelector, but they don't need update" + } + return updateMeta{ + flow: ytv1.UpdateFlowStateless, + componentNames: execNodeNames, + }, "" + case ytv1.UpdateSelectorStatelessOnly: + if !statelessNeedUpdate { + return updateMeta{}, "Only stateless components update is allowed by updateSelector, but they don't need update" + } + return updateMeta{ + flow: ytv1.UpdateFlowStateless, + componentNames: statelessNames, + }, "" + default: + // TODO: just validate it in hook + return updateMeta{}, fmt.Sprintf("Unexpected update selector %s", configuredSelector) } - return getComponentNames(needUpdate), "" } func (r *YtsaurusReconciler) Sync(ctx context.Context, resource *ytv1.Ytsaurus) (ctrl.Result, error) { @@ -314,23 +545,35 @@ func (r *YtsaurusReconciler) Sync(ctx context.Context, resource *ytv1.Ytsaurus) return ctrl.Result{Requeue: true}, err case componentManager.needUpdate() != nil: - componentNames, blockMsg := chooseUpdateStrategy(ytsaurus.GetResource().Spec, componentManager.needUpdate()) + meta, blockMsg := chooseUpdateFlow(ytsaurus.GetResource().Spec, componentManager.needUpdate()) if blockMsg != "" { logger.Info(blockMsg) - return ctrl.Result{}, nil + return ctrl.Result{Requeue: true}, nil } - logger.Info("Ytsaurus needs components update", "components", componentNames) - err := ytsaurus.SaveUpdatingClusterState(ctx, componentNames) - return ctrl.Result{Requeue: true}, err + logger.Info("Ytsaurus needs components update", + "components", meta.componentNames, + "flow", meta.flow, + ) + err = ytsaurus.SaveUpdatingClusterState(ctx, meta.flow, meta.componentNames) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil } case ytv1.ClusterStateUpdating: var result *ctrl.Result var err error - if ytsaurus.GetLocalUpdatingComponents() != nil { - result, err = r.handleUpdatingStateLocalMode(ctx, ytsaurus, componentManager) - } else { - result, err = r.handleUpdatingStateFullMode(ctx, ytsaurus, componentManager) + + switch ytsaurus.GetUpdateFlow() { + case ytv1.UpdateFlowFull: + result, err = r.handleEverything(ctx, ytsaurus, componentManager) + case ytv1.UpdateFlowStateless: + result, err = r.handleStateless(ctx, ytsaurus, componentManager) + case ytv1.UpdateFlowMaster: + result, err = r.handleMasterOnly(ctx, ytsaurus, componentManager) + case ytv1.UpdateFlowTabletNodes: + result, err = r.handleTabletNodesOnly(ctx, ytsaurus, componentManager) } if result != nil { diff --git a/pkg/apiproxy/ytsaurus.go b/pkg/apiproxy/ytsaurus.go index 933f7cb3..e4bc37d7 100644 --- a/pkg/apiproxy/ytsaurus.go +++ b/pkg/apiproxy/ytsaurus.go @@ -58,6 +58,10 @@ func (c *Ytsaurus) GetLocalUpdatingComponents() []string { return c.ytsaurus.Status.UpdateStatus.Components } +func (c *Ytsaurus) GetUpdateFlow() ytv1.UpdateFlow { + return c.ytsaurus.Status.UpdateStatus.Flow +} + func (c *Ytsaurus) IsUpdateStatusConditionTrue(condition string) bool { return meta.IsStatusConditionTrue(c.ytsaurus.Status.UpdateStatus.Conditions, condition) } @@ -73,6 +77,7 @@ func (c *Ytsaurus) ClearUpdateStatus(ctx context.Context) error { c.ytsaurus.Status.UpdateStatus.TabletCellBundles = make([]ytv1.TabletCellBundleInfo, 0) c.ytsaurus.Status.UpdateStatus.MasterMonitoringPaths = make([]string, 0) c.ytsaurus.Status.UpdateStatus.Components = nil + c.ytsaurus.Status.UpdateStatus.Flow = ytv1.UpdateFlowNone return c.apiProxy.UpdateStatus(ctx) } @@ -82,9 +87,10 @@ func (c *Ytsaurus) LogUpdate(ctx context.Context, message string) { logger.Info(fmt.Sprintf("Ytsaurus update: %s", message)) } -func (c *Ytsaurus) SaveUpdatingClusterState(ctx context.Context, components []string) error { +func (c *Ytsaurus) SaveUpdatingClusterState(ctx context.Context, flow ytv1.UpdateFlow, components []string) error { logger := log.FromContext(ctx) c.ytsaurus.Status.State = ytv1.ClusterStateUpdating + c.ytsaurus.Status.UpdateStatus.Flow = flow c.ytsaurus.Status.UpdateStatus.Components = components if err := c.apiProxy.UpdateStatus(ctx); err != nil { diff --git a/pkg/components/ytsaurus_client.go b/pkg/components/ytsaurus_client.go index 0ee7f31f..deb92b45 100644 --- a/pkg/components/ytsaurus_client.go +++ b/pkg/components/ytsaurus_client.go @@ -413,10 +413,6 @@ func (yc *YtsaurusClient) GetYtClient() yt.Client { } func (yc *YtsaurusClient) HandlePossibilityCheck(ctx context.Context) (ok bool, msg string, err error) { - if !yc.ytsaurus.GetResource().Spec.EnableFullUpdate { - return false, "Full update is not enabled", nil - } - // Check tablet cell bundles. notGoodBundles, err := GetNotGoodTabletCellBundles(ctx, yc.ytClient) if err != nil { From 7984dc06ca8f77ca9def2347454972ca64906d37 Mon Sep 17 00:00:00 2001 From: Kirill Sibirev Date: Mon, 22 Apr 2024 10:35:41 +0200 Subject: [PATCH 6/6] Unspecified --- controllers/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/sync.go b/controllers/sync.go index fbd506bb..e03d7011 100644 --- a/controllers/sync.go +++ b/controllers/sync.go @@ -441,7 +441,7 @@ func chooseUpdateFlow(spec ytv1.YtsaurusSpec, needUpdate []components.Component) allNamesNeedingUpdate := getComponentNames(needUpdate) // Fallback to EnableFullUpdate field. - if configuredSelector == ytv1.UpdateSelectorNone { + if configuredSelector == ytv1.UpdateSelectorUnspecified { if statefulNeedUpdate { if isFullUpdateEnabled { return updateMeta{flow: ytv1.UpdateFlowFull, componentNames: nil}, ""