Skip to content

Commit

Permalink
Add support for complex ScalingRules in autoscaler
Browse files Browse the repository at this point in the history
Signed-off-by: Arnob kumar saha <[email protected]>
  • Loading branch information
ArnobKumarSaha committed Apr 26, 2024
1 parent 104a820 commit e77d7b0
Show file tree
Hide file tree
Showing 13 changed files with 766 additions and 33 deletions.
22 changes: 22 additions & 0 deletions apis/autoscaling/v1alpha1/mongodb_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ func (in *MongoDBAutoscaler) validate() error {
if st.Standalone != nil {
return errors.New("Spec.Storage.Standalone is invalid for sharded mongoDB")
}

if err = validateScalingRules(st.ConfigServer); err != nil {
return err
}
if err = validateScalingRules(st.Shard); err != nil {
return err
}

} else if mg.Spec.ReplicaSet != nil {
if st.Standalone != nil {
return errors.New("Spec.Storage.Standalone is invalid for replicaSet mongoDB")
Expand All @@ -200,6 +208,10 @@ func (in *MongoDBAutoscaler) validate() error {
if st.ConfigServer != nil {
return errors.New("Spec.Storage.ConfigServer is invalid for replicaSet mongoDB")
}

if err = validateScalingRules(st.ReplicaSet); err != nil {
return err
}
} else {
if st.ReplicaSet != nil {
return errors.New("Spec.Storage.Replicaset is invalid for Standalone mongoDB")
Expand All @@ -213,6 +225,16 @@ func (in *MongoDBAutoscaler) validate() error {
if st.Hidden != nil {
return errors.New("Spec.Storage.Hidden is invalid for Standalone mongoDB")
}

if err = validateScalingRules(st.Standalone); err != nil {
return err
}
}

if mg.Spec.Hidden != nil {
if err = validateScalingRules(st.Hidden); err != nil {
return err
}
}
}

Expand Down
86 changes: 86 additions & 0 deletions apis/autoscaling/v1alpha1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions apis/autoscaling/v1alpha1/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
opsapi "kubedb.dev/apimachinery/apis/ops/v1alpha1"

core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kmapi "kmodules.xyz/client-go/api/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -109,16 +110,37 @@ type StorageAutoscalerSpec struct {

// If PVC usage percentage is less than the UsageThreshold,
// we don't need to scale it. The Default is 80%
UsageThreshold int32 `json:"usageThreshold,omitempty"`
UsageThreshold *int32 `json:"usageThreshold,omitempty"`

// If PVC usage percentage >= UsageThreshold,
// we need to scale that by ScalingThreshold percentage. The Default is 50%
ScalingThreshold int32 `json:"scalingThreshold,omitempty"`
ScalingThreshold *int32 `json:"scalingThreshold,omitempty"`

// ScalingRules are to support more dynamic ScalingThreshold
// For example, Upto certain Size (GB) increase in %, after that increase in absolute value.
ScalingRules []StorageScalingRule `json:"scalingRules,omitempty"`

// Set a max size limit for volume increase
UpperBound resource.Quantity `json:"upperBound,omitempty"`

// ExpansionMode can be `Online` or `Offline`
ExpansionMode opsapi.VolumeExpansionMode `json:"expansionMode"`
}

// StorageScalingRule format:
// - appliesBelow: 500GB
// threshold: 30pc
// - appliesBelow: 1000GB
// threshold: 20pc
// - appliesBelow: ""
// threshold: 50GB
//
// Note that, `pc` & `%` both is supported
type StorageScalingRule struct {
AppliesUpto string `json:"appliesUpto"`
Threshold string `json:"threshold"`
}

// AutoscalerStatus describes the runtime state of the autoscaler.
type AutoscalerStatus struct {
// Specifies the current phase of the autoscaler
Expand Down
93 changes: 89 additions & 4 deletions apis/autoscaling/v1alpha1/webhook_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ limitations under the License.
package v1alpha1

import (
"fmt"
"sort"
"strconv"
"strings"

dbapi "kubedb.dev/apimachinery/apis/kubedb/v1alpha2"

core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"
)

func setDefaultStorageValues(storageSpec *StorageAutoscalerSpec) {
Expand All @@ -30,12 +38,56 @@ func setDefaultStorageValues(storageSpec *StorageAutoscalerSpec) {
if storageSpec.Trigger == "" {
storageSpec.Trigger = AutoscalerTriggerOff
}
if storageSpec.ScalingThreshold == 0 {
storageSpec.ScalingThreshold = DefaultStorageScalingThreshold
if storageSpec.ScalingThreshold == nil {
ptr.To(DefaultStorageScalingThreshold)
storageSpec.ScalingThreshold = ptr.To[int32](DefaultStorageScalingThreshold)
}
defaultScalingRules(storageSpec)
}

type quantity struct {
InString string
InQuantity resource.Quantity
Threshold string
}

func defaultScalingRules(storageSpec *StorageAutoscalerSpec) {
if storageSpec.UsageThreshold == nil {
storageSpec.UsageThreshold = ptr.To[int32](DefaultStorageUsageThreshold)
}
var quantities []quantity
var zeroQuantityThresholds []string
for _, sr := range storageSpec.ScalingRules {
if sr.AppliesUpto == "" {
zeroQuantityThresholds = append(zeroQuantityThresholds, sr.Threshold)
continue
}
quantities = append(quantities, quantity{
InString: sr.AppliesUpto,
InQuantity: resource.MustParse(sr.AppliesUpto),
Threshold: "",
})
}

// Sort the quantities
sort.Slice(quantities, func(i, j int) bool {
return quantities[i].InQuantity.Cmp(quantities[j].InQuantity) < 0
})

storageSpec.ScalingRules = make([]StorageScalingRule, 0)
for _, q := range quantities {
storageSpec.ScalingRules = append(storageSpec.ScalingRules, StorageScalingRule{
AppliesUpto: q.InString,
Threshold: q.Threshold,
})
}
if storageSpec.UsageThreshold == 0 {
storageSpec.UsageThreshold = DefaultStorageUsageThreshold
for _, threshold := range zeroQuantityThresholds {
storageSpec.ScalingRules = append(storageSpec.ScalingRules, StorageScalingRule{
AppliesUpto: "",
Threshold: threshold,
})
}
klog.Infof("scaling Rules = %v \n", storageSpec.ScalingRules)
}

func setDefaultComputeValues(computeSpec *ComputeAutoscalerSpec) {
Expand Down Expand Up @@ -75,3 +127,36 @@ func setInMemoryDefaults(computeSpec *ComputeAutoscalerSpec, storageEngine dbapi
computeSpec.InMemoryStorage.ScalingFactorPercentage = DefaultInMemoryStorageScalingFactorPercentage
}
}

func validateScalingRules(storageSpec *StorageAutoscalerSpec) error {
var zeroQuantityThresholds []string
for _, sr := range storageSpec.ScalingRules {
if sr.AppliesUpto == "" {
zeroQuantityThresholds = append(zeroQuantityThresholds, sr.Threshold)
}
th := sr.Threshold
if strings.HasSuffix(th, "%") {
if !isNum(strings.TrimSuffix(th, "%")) {
return fmt.Errorf("%v is not a valid percentage value", th)
}
} else if strings.HasSuffix(th, "pc") {
if !isNum(strings.TrimSuffix(th, "pc")) {
return fmt.Errorf("%v is not a valid percentage value", th)
}
} else {
_, err := resource.ParseQuantity(sr.Threshold)
if err != nil {
return fmt.Errorf("%v is not a valid quatity", sr.Threshold)
}
}
}
if len(zeroQuantityThresholds) > 1 {
return fmt.Errorf("%v appliesBelow value are empty in %v", zeroQuantityThresholds, storageSpec.ScalingRules)
}
return nil
}

func isNum(s string) bool {
_, err := strconv.Atoi(s)
return err == nil
}
Loading

0 comments on commit e77d7b0

Please sign in to comment.