Skip to content
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

feat: Optimize UnitedDeployment replicas settings #1247

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions apis/apps/defaults/v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,6 @@ func SetDefaultsBroadcastJob(obj *v1alpha1.BroadcastJob, injectTemplateDefaults

// SetDefaults_UnitedDeployment set default values for UnitedDeployment.
func SetDefaultsUnitedDeployment(obj *v1alpha1.UnitedDeployment, injectTemplateDefaults bool) {
if obj.Spec.Replicas == nil {
obj.Spec.Replicas = utilpointer.Int32Ptr(1)
}
if obj.Spec.RevisionHistoryLimit == nil {
obj.Spec.RevisionHistoryLimit = utilpointer.Int32Ptr(10)
}
Expand Down
38 changes: 23 additions & 15 deletions pkg/controller/uniteddeployment/allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,16 @@ func (n subsetInfos) Swap(i, j int) {
// new replicas indicated from UnitedDeployment.Spec.Topology.Subsets.
func GetAllocatedReplicas(nameToSubset *map[string]*Subset, ud *appsv1alpha1.UnitedDeployment) (*map[string]int32, error) {
subsetInfos := getSubsetInfos(nameToSubset, ud)
specifiedReplicas := getSpecifiedSubsetReplicas(ud)

var expectedReplicas int32 = -1
if ud.Spec.Replicas != nil {
expectedReplicas = *ud.Spec.Replicas
}

specifiedReplicas := getSpecifiedSubsetReplicas(expectedReplicas, ud)
klog.V(4).Infof("UnitedDeployment %s/%s specifiedReplicas: %v", ud.Namespace, ud.Name, specifiedReplicas)
// call SortToAllocator to sort all subset by subset.Replicas in order of increment
return subsetInfos.SortToAllocator().AllocateReplicas(*ud.Spec.Replicas, specifiedReplicas)
return subsetInfos.SortToAllocator().AllocateReplicas(expectedReplicas, specifiedReplicas)
}

func (n subsetInfos) SortToAllocator() *replicasAllocator {
Expand All @@ -84,27 +90,29 @@ func (s *replicasAllocator) validateReplicas(replicas int32, subsetReplicasLimit
specifiedReplicas += replicas
}

if specifiedReplicas > replicas {
return fmt.Errorf("specified subsets' replica (%d) is greater than UnitedDeployment replica (%d)",
specifiedReplicas, replicas)
} else if specifiedReplicas < replicas {
specifiedCount := 0
for _, subset := range *s.subsets {
if _, exist := (*subsetReplicasLimits)[subset.SubsetName]; exist {
specifiedCount++
}
specifiedCount := 0
for _, subset := range *s.subsets {
if _, exist := (*subsetReplicasLimits)[subset.SubsetName]; exist {
specifiedCount++
}

if specifiedCount == len(*s.subsets) {
}
klog.V(4).Infof("specifiedCount: %d, len(*s.subsets): %d", specifiedCount, len(*s.subsets))
if replicas != -1 {
if specifiedReplicas > replicas {
return fmt.Errorf("specified subsets' replica (%d) is greater than UnitedDeployment replica (%d)",
specifiedReplicas, replicas)
} else if specifiedReplicas < replicas && specifiedCount == len(*s.subsets) {
return fmt.Errorf("specified subsets' replica (%d) is less than UnitedDeployment replica (%d)",
specifiedReplicas, replicas)
}
} else if specifiedCount != len(*s.subsets) {
return fmt.Errorf("all subsets must be specified when UnitedDeployment replica is unspecified")
}

return nil
}

func getSpecifiedSubsetReplicas(ud *appsv1alpha1.UnitedDeployment) *map[string]int32 {
func getSpecifiedSubsetReplicas(replicas int32, ud *appsv1alpha1.UnitedDeployment) *map[string]int32 {
replicaLimits := map[string]int32{}
if ud.Spec.Topology.Subsets == nil {
return &replicaLimits
Expand All @@ -115,7 +123,7 @@ func getSpecifiedSubsetReplicas(ud *appsv1alpha1.UnitedDeployment) *map[string]i
continue
}

if specifiedReplicas, err := ParseSubsetReplicas(*ud.Spec.Replicas, *subsetDef.Replicas); err == nil {
if specifiedReplicas, err := ParseSubsetReplicas(replicas, *subsetDef.Replicas); err == nil {
replicaLimits[subsetDef.Name] = specifiedReplicas
} else {
klog.Warningf("Fail to consider the replicas of subset %s when parsing replicaLimits during managing replicas of UnitedDeployment %s/%s: %s",
Expand Down
17 changes: 17 additions & 0 deletions pkg/controller/uniteddeployment/allocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ func TestSpecifyValidReplicas(t *testing.T) {
if " t1 -> 1; t2 -> 2; t3 -> 3; t4 -> 4;" != allocator.String() {
t.Fatalf("unexpected %s", allocator)
}

infos = subsetInfos{
createSubset("t1", 4),
createSubset("t2", 2),
createSubset("t3", 2),
createSubset("t4", 2),
}
allocator = infos.SortToAllocator()
allocator.AllocateReplicas(-1, &map[string]int32{
"t1": 1,
"t2": 2,
"t3": 3,
"t4": 4,
})
if " t1 -> 1; t2 -> 2; t3 -> 3; t4 -> 4;" != allocator.String() {
t.Fatalf("unexpected %s", allocator)
}
}

func TestSpecifyInvalidReplicas(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func ParseSubsetReplicas(udReplicas int32, subsetReplicas intstr.IntOrString) (i
return subsetReplicas.IntVal, nil
}

if udReplicas < 0 {
return 0, fmt.Errorf("subsetReplicas (%v) should not be string when unitedDeployment replicas is empty", subsetReplicas.StrVal)
}

strVal := subsetReplicas.StrVal
if !strings.HasSuffix(strVal, "%") {
return 0, fmt.Errorf("subset replicas (%s) only support integer value or percentage value with a suffix '%%'", strVal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ import (
func validateUnitedDeploymentSpec(spec *appsv1alpha1.UnitedDeploymentSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}

allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Replicas), fldPath.Child("replicas"))...)
if spec.Replicas != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Replicas), fldPath.Child("replicas"))...)
}
if spec.Selector == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
} else {
Expand All @@ -61,12 +63,13 @@ func validateUnitedDeploymentSpec(spec *appsv1alpha1.UnitedDeploymentSpec, fldPa
}

var sumReplicas int32
var expectedReplicas int32 = 1
var expectedReplicas int32 = -1
if spec.Replicas != nil {
expectedReplicas = *spec.Replicas
}
subSetNames := sets.String{}
count := 0
subSetNames := sets.String{}

for i, subset := range spec.Topology.Subsets {
if len(subset.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("topology", "subsets").Index(i).Child("name"), ""))
Expand Down Expand Up @@ -115,13 +118,18 @@ func validateUnitedDeploymentSpec(spec *appsv1alpha1.UnitedDeploymentSpec, fldPa
}
}

// sum of subset replicas may be less than uniteddployment replicas
if sumReplicas > expectedReplicas {
allErrs = append(allErrs, field.Invalid(fldPath.Child("topology", "subsets"), sumReplicas, fmt.Sprintf("sum of indicated subset replicas %d should not be greater than UnitedDeployment replicas %d", sumReplicas, expectedReplicas)))
}
if expectedReplicas != -1 {
// sum of subset replicas may be less than uniteddployment replicas
if sumReplicas > expectedReplicas {
allErrs = append(allErrs, field.Invalid(fldPath.Child("topology", "subsets"), sumReplicas, fmt.Sprintf("sum of indicated subset replicas %d should not be greater than UnitedDeployment replicas %d", sumReplicas, expectedReplicas)))
}

if count > 0 && count == len(spec.Topology.Subsets) && sumReplicas != expectedReplicas {
allErrs = append(allErrs, field.Invalid(fldPath.Child("topology", "subsets"), sumReplicas, fmt.Sprintf("if replicas of all subsets are provided, the sum of indicated subset replicas %d should equal UnitedDeployment replicas %d", sumReplicas, expectedReplicas)))
if count > 0 && count == len(spec.Topology.Subsets) && sumReplicas != expectedReplicas {
allErrs = append(allErrs, field.Invalid(fldPath.Child("topology", "subsets"), sumReplicas, fmt.Sprintf("if replicas of all subsets are provided, the sum of indicated subset replicas %d should equal UnitedDeployment replicas %d", sumReplicas, expectedReplicas)))
}
} else if count != len(spec.Topology.Subsets) {
// validate all of subsets replicas are not nil
allErrs = append(allErrs, field.Invalid(fldPath.Child("topology", "subsets"), sumReplicas, "if UnitedDeployment replicas is not provided, replicas of all subsets should be provided"))
}

if spec.UpdateStrategy.ManualUpdate != nil {
Expand All @@ -146,7 +154,9 @@ func validateUnitedDeployment(unitedDeployment *appsv1alpha1.UnitedDeployment) f
func ValidateUnitedDeploymentUpdate(unitedDeployment, oldUnitedDeployment *appsv1alpha1.UnitedDeployment) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&unitedDeployment.ObjectMeta, &oldUnitedDeployment.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, validateUnitedDeploymentSpecUpdate(&unitedDeployment.Spec, &oldUnitedDeployment.Spec, field.NewPath("spec"))...)
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*unitedDeployment.Spec.Replicas), field.NewPath("spec", "replicas"))...)
if unitedDeployment.Spec.Replicas != nil {
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*unitedDeployment.Spec.Replicas), field.NewPath("spec", "replicas"))...)
}
return allErrs
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,34 @@ func TestValidateUnitedDeployment(t *testing.T) {
replicas3 := intstr.FromString("71%")
replicas4 := intstr.FromString("29%")
successCases := []appsv1alpha1.UnitedDeployment{
{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: appsv1alpha1.UnitedDeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: appsv1alpha1.SubsetTemplate{
StatefulSetTemplate: &appsv1alpha1.StatefulSetTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,
},
Spec: apps.StatefulSetSpec{
Template: validPodTemplate.Template,
},
},
},
Topology: appsv1alpha1.Topology{
Subsets: []appsv1alpha1.Subset{
{
Name: "subset1",
Replicas: &replicas1,
},
{
Name: "subset2",
Replicas: &replicas1,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: appsv1alpha1.UnitedDeploymentSpec{
Expand Down Expand Up @@ -424,6 +452,34 @@ func TestValidateUnitedDeployment(t *testing.T) {
},
},
},
"subset replicas type is percent when spec replicas not set": {
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: appsv1alpha1.UnitedDeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: appsv1alpha1.SubsetTemplate{
StatefulSetTemplate: &appsv1alpha1.StatefulSetTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validLabels,
},
Spec: apps.StatefulSetSpec{
Template: validPodTemplate.Template,
},
},
},
Topology: appsv1alpha1.Topology{
Subsets: []appsv1alpha1.Subset{
{
Name: "subset1",
Replicas: &replicas2,
},
{
Name: "subset2",
Replicas: &replicas1,
},
},
},
},
},
"partition not exist": {
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: appsv1alpha1.UnitedDeploymentSpec{
Expand Down Expand Up @@ -583,6 +639,7 @@ func TestValidateUnitedDeployment(t *testing.T) {
field != "spec.topology.subsets" &&
field != "spec.topology.subsets[0]" &&
field != "spec.topology.subsets[0].name" &&
field != "spec.topology.subsets[0].replicas" &&
field != "spec.updateStrategy.partitions" &&
field != "spec.topology.subsets[0].nodeSelectorTerm.matchExpressions[0].values" {
t.Errorf("%s: missing prefix for: %v", k, errs[i])
Expand Down Expand Up @@ -974,10 +1031,6 @@ func TestValidateUnitedDeploymentUpdate(t *testing.T) {
}

func setTestDefault(obj *appsv1alpha1.UnitedDeployment) {
if obj.Spec.Replicas == nil {
obj.Spec.Replicas = new(int32)
*obj.Spec.Replicas = 1
}
if obj.Spec.RevisionHistoryLimit == nil {
obj.Spec.RevisionHistoryLimit = new(int32)
*obj.Spec.RevisionHistoryLimit = 10
Expand Down