-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multiple update selectors #383
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -433,61 +433,81 @@ | |
componentNames []string | ||
} | ||
|
||
func canUpdateComponent(selector ytv1.UpdateSelector, component consts.ComponentType) bool { | ||
switch selector { | ||
case ytv1.UpdateSelectorNothing: | ||
return false | ||
case ytv1.UpdateSelectorMasterOnly: | ||
return component == consts.MasterType | ||
case ytv1.UpdateSelectorDataNodesOnly: | ||
return component == consts.DataNodeType | ||
case ytv1.UpdateSelectorTabletNodesOnly: | ||
return component == consts.TabletNodeType | ||
case ytv1.UpdateSelectorExecNodesOnly: | ||
return component == consts.ExecNodeType | ||
case ytv1.UpdateSelectorStatelessOnly: | ||
switch component { | ||
case consts.MasterType: | ||
return false | ||
case consts.DataNodeType: | ||
return false | ||
case consts.TabletNodeType: | ||
return false | ||
} | ||
return true | ||
case ytv1.UpdateSelectorEverything: | ||
return true | ||
default: | ||
return false | ||
func getFlowFromComponent(component consts.ComponentType) ytv1.UpdateFlow { | ||
if component == consts.MasterType { | ||
return ytv1.UpdateFlowMaster | ||
} | ||
if component == consts.TabletNodeType { | ||
return ytv1.UpdateFlowTabletNodes | ||
} | ||
if component == consts.DataNodeType || component == consts.ExecNodeType { | ||
return ytv1.UpdateFlowFull | ||
} | ||
return ytv1.UpdateFlowStateless | ||
} | ||
l0kix2 marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we have combination of selectors that wouldn't be possible to cover with existing flow (like like The thing which is bothers me that this flows is not test covered very well and it is rather expensive (in the meaning of test time and code amount) to test all the required cases in e2e.
and have some function to build flow from the list of exact components which require update func buildFlow(components) []flowStep{
var steps []flowStep
if components.Contain(masterType) {
steps = append(flowStep{
name: "safe mode enabled",
condition: func(ytsaurus ytv1.Ytsaurus) {
return resource.Status.UpdateStatus.State == `ytv1.UpdateStateWaitingForSafeModeEnabled`
},
onSuccess: func(ytsaurus ytv1.Ytsaurus) {
return ytsaurus.SaveUpdateState(ctx, ytv1.UpdateStateWaitingForTabletCellsSaving)
}
})
}
...
return steps
} (I'm not sure that this exact fields and functions should be there, but something similar should work). That way we can cover our flow building with fast unittest good enough and cover all the important cases and be sure not to break stuff in flow logic. So the whole code for this PR will look like this:
all 1-3 steps can be untitested that way. I think it is better to do it in the separate PR and after that use that code in this PR. |
||
|
||
func canUpdateComponent(selectors []ytv1.ComponentUpdateSelector, component components.Component) (bool, error) { | ||
for _, selector := range selectors { | ||
if selector.ComponentType != "" { | ||
if selector.ComponentType == component.GetType() { | ||
return true, nil | ||
} | ||
} else if selector.ComponentName != "" { | ||
if selector.ComponentName == component.GetName() { | ||
return true, nil | ||
} | ||
} else { | ||
switch selector.ComponentGroup { | ||
case consts.ComponentGroupEverything: | ||
return true, nil | ||
case consts.ComponentGroupNothing: | ||
return false, nil | ||
case consts.ComponentGroupStateful: | ||
if component.GetType() == consts.DataNodeType || component.GetType() == consts.TabletNodeType { | ||
return true, nil | ||
} | ||
case consts.ComponentGroupStateless: | ||
if component.GetType() != consts.DataNodeType && component.GetType() != consts.TabletNodeType && component.GetType() != consts.MasterType { | ||
return true, nil | ||
} | ||
default: | ||
return false, fmt.Errorf("unexpected component group %s", selector.ComponentGroup) | ||
} | ||
} | ||
} | ||
return false, nil | ||
} | ||
|
||
// 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) { | ||
configuredSelector := spec.UpdateSelector | ||
if configuredSelector == ytv1.UpdateSelectorUnspecified { | ||
if spec.EnableFullUpdate { | ||
configuredSelector = ytv1.UpdateSelectorEverything | ||
} else { | ||
configuredSelector = ytv1.UpdateSelectorStatelessOnly | ||
} | ||
configuredSelectors := spec.UpdateSelectors | ||
if len(configuredSelectors) == 0 && spec.EnableFullUpdate { | ||
configuredSelectors = []ytv1.ComponentUpdateSelector{{ComponentGroup: consts.ComponentGroupEverything}} | ||
} | ||
needFullUpdate := false | ||
|
||
var canUpdate []string | ||
var cannotUpdate []string | ||
needFullUpdate := false | ||
flows := make(map[ytv1.UpdateFlow]struct{}) | ||
|
||
for _, comp := range needUpdate { | ||
componentType := comp.GetType() | ||
componentName := comp.GetName() | ||
if canUpdateComponent(configuredSelector, componentType) { | ||
upd, err := canUpdateComponent(configuredSelectors, comp) | ||
if err != nil { | ||
return updateMeta{}, err.Error() | ||
} | ||
if upd { | ||
canUpdate = append(canUpdate, componentName) | ||
flows[getFlowFromComponent(componentType)] = struct{}{} | ||
} else { | ||
cannotUpdate = append(cannotUpdate, componentName) | ||
} | ||
if !canUpdateComponent(ytv1.UpdateSelectorStatelessOnly, componentType) && componentType != consts.DataNodeType { | ||
|
||
statelessCheck, err := canUpdateComponent([]ytv1.ComponentUpdateSelector{{ComponentGroup: consts.ComponentGroupStateless}}, comp) | ||
if !statelessCheck && componentType != consts.DataNodeType { | ||
needFullUpdate = true | ||
} | ||
} | ||
|
@@ -499,37 +519,25 @@ | |
return updateMeta{}, "All components are uptodate" | ||
} | ||
|
||
switch configuredSelector { | ||
case ytv1.UpdateSelectorEverything: | ||
if len(configuredSelectors) == 1 && configuredSelectors[0].ComponentGroup == consts.ComponentGroupEverything { | ||
if needFullUpdate { | ||
return updateMeta{ | ||
flow: ytv1.UpdateFlowFull, | ||
componentNames: nil, | ||
}, "" | ||
return updateMeta{flow: ytv1.UpdateFlowFull, componentNames: nil}, "" | ||
} else { | ||
return updateMeta{ | ||
flow: ytv1.UpdateFlowStateless, | ||
componentNames: canUpdate, | ||
}, "" | ||
} | ||
case ytv1.UpdateSelectorMasterOnly: | ||
return updateMeta{ | ||
flow: ytv1.UpdateFlowMaster, | ||
componentNames: canUpdate, | ||
}, "" | ||
case ytv1.UpdateSelectorTabletNodesOnly: | ||
return updateMeta{ | ||
flow: ytv1.UpdateFlowTabletNodes, | ||
componentNames: canUpdate, | ||
}, "" | ||
case ytv1.UpdateSelectorDataNodesOnly, ytv1.UpdateSelectorExecNodesOnly, ytv1.UpdateSelectorStatelessOnly: | ||
return updateMeta{ | ||
flow: ytv1.UpdateFlowStateless, | ||
componentNames: canUpdate, | ||
}, "" | ||
default: | ||
return updateMeta{}, fmt.Sprintf("Unexpected update selector %s", configuredSelector) | ||
return updateMeta{flow: ytv1.UpdateFlowStateless, componentNames: canUpdate}, "" | ||
} | ||
} | ||
|
||
if len(flows) == 0 { | ||
return updateMeta{}, fmt.Sprintf("Unexpected state: no flows for components {%s}", strings.Join(canUpdate, ", ")) | ||
} | ||
|
||
if len(flows) == 1 { | ||
for flow := range flows { | ||
return updateMeta{flow: flow, componentNames: canUpdate}, "" | ||
} | ||
} | ||
// If more than one flow is possible, we choose to follow full update flow. | ||
return updateMeta{flow: ytv1.UpdateFlowFull, componentNames: canUpdate}, "" | ||
} | ||
|
||
func (r *YtsaurusReconciler) Sync(ctx context.Context, resource *ytv1.Ytsaurus) (ctrl.Result, error) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not remove
updateSelector
stuff for now. We are shooting ourselves in the leg that way, since we ourselves need some time to migrate fromupdateSelector
toupdateSelectors
.Let's just add deprecation comment and remove it it next releases when we migrate.
I suggest we have some function, which would receive ytsaurus spec and return
updateSelectors
values.It should be easy to unittest it for all major cases, which are:
updateSelectors
is not nil/empty list — we return it as isupdateSelectors
is nil/empty list andupdateSelector != UpdateSelectorUnspecified
— we convert it's value to theupdateSelectors
value. Rules are:updateSelectors
is nil/empty list andupdateSelector == UpdateSelectorUnspecified
we convertenableFullUpdate
to theupdateSelectors
value. Rules are: