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

Minimum validation for multitenant formations #5199

Merged
merged 14 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/IBM/apigateway-go-sdk v0.0.0-20210714141226-a5d5d49caaca
github.com/IBM/appconfiguration-go-admin-sdk v0.3.0
github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f
github.com/IBM/cloud-databases-go-sdk v0.5.0
github.com/IBM/cloud-databases-go-sdk v0.6.0
github.com/IBM/cloudant-go-sdk v0.0.43
github.com/IBM/code-engine-go-sdk v0.0.0-20231106200405-99e81b3ee752
github.com/IBM/container-registry-go-sdk v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f h1:4c1
github.com/IBM/appid-management-go-sdk v0.0.0-20210908164609-dd0e0eaf732f/go.mod h1:d22kTYY7RYBWcQlZpqrSdshpB/lJ16viWS5Sbjtlc8s=
github.com/IBM/cloud-databases-go-sdk v0.5.0 h1:Bie6MnT1jLchQmtKVA20HHETTPdlOR+i11P2kJ55viM=
github.com/IBM/cloud-databases-go-sdk v0.5.0/go.mod h1:nCIVfeZnhBYIiwByT959dFP4VWUeNLxomDYy63tTC6M=
github.com/IBM/cloud-databases-go-sdk v0.6.0 h1:QK3eif7+kusgeuMB54Zw5nco/kDwsDg2sD/84/foDxo=
github.com/IBM/cloud-databases-go-sdk v0.6.0/go.mod h1:nCIVfeZnhBYIiwByT959dFP4VWUeNLxomDYy63tTC6M=
github.com/IBM/cloudant-go-sdk v0.0.43 h1:YxTy4RpAEezX32YIWnds76hrBREmO4u6IkBz1WylNuQ=
github.com/IBM/cloudant-go-sdk v0.0.43/go.mod h1:WeYrJPaHTw19943ndWnVfwMIlZ5z0XUM2uEXNBrwZ1M=
github.com/IBM/code-engine-go-sdk v0.0.0-20231106200405-99e81b3ee752 h1:S5NT0aKKUqd9hnIrPN/qUijKx9cZjJi3kfFpog0ByDA=
Expand Down
164 changes: 140 additions & 24 deletions ibm/service/database/resource_ibm_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,16 @@ func ResourceIBMDatabaseInstance() *schema.Resource {
Computed: true,
Description: "Can memory scale down as well as up.",
},
"cpu_enforcement_ratio_ceiling_mb": {
Type: schema.TypeInt,
Optional: true,
Description: "The amount of memory required before the cpu/memory ratio is no longer enforced. (multitenant only).",
},
"cpu_enforcement_ratio_mb": {
Type: schema.TypeInt,
Optional: true,
Description: "The maximum memory allowed per CPU until the ratio ceiling is reached. (multitenant only).",
},
},
},
},
Expand Down Expand Up @@ -987,21 +997,23 @@ type Group struct {
}

type GroupResource struct {
Units string
Allocation int
Minimum int
Maximum int
StepSize int
IsAdjustable bool
IsOptional bool
CanScaleDown bool
Units string
Allocation int
Minimum int
Maximum int
StepSize int
IsAdjustable bool
IsOptional bool
CanScaleDown bool
CPUEnforcementRatioCeilingMb int
CPUEnforcementRatioMb int
}

type HostFlavorGroupResource struct {
ID string
}

func getDefaultScalingGroups(_service string, _plan string, meta interface{}) (groups []clouddatabasesv5.Group, err error) {
func getDefaultScalingGroups(_service string, _plan string, _hostFlavor string, meta interface{}) (groups []clouddatabasesv5.Group, err error) {
cloudDatabasesClient, err := meta.(conns.ClientSession).CloudDatabasesV5()
if err != nil {
return groups, fmt.Errorf("[ERROR] Error getting database client settings: %s", err)
Expand Down Expand Up @@ -1033,6 +1045,9 @@ func getDefaultScalingGroups(_service string, _plan string, meta interface{}) (g
}

getDefaultScalingGroupsOptions := cloudDatabasesClient.NewGetDefaultScalingGroupsOptions(service)
if _hostFlavor != "" {
getDefaultScalingGroupsOptions.SetHostFlavor(_hostFlavor)
}

getDefaultScalingGroupsResponse, _, err := cloudDatabasesClient.GetDefaultScalingGroups(getDefaultScalingGroupsOptions)
if err != nil {
Expand All @@ -1043,7 +1058,7 @@ func getDefaultScalingGroups(_service string, _plan string, meta interface{}) (g
}

func getInitialNodeCount(service string, plan string, meta interface{}) (int, error) {
groups, err := getDefaultScalingGroups(service, plan, meta)
groups, err := getDefaultScalingGroups(service, plan, "", meta)

if err != nil {
return 0, err
Expand Down Expand Up @@ -2702,14 +2717,16 @@ func normalizeGroups(_groups []clouddatabasesv5.Group) (groups []Group) {
}

group.Memory = &GroupResource{
Units: *g.Memory.Units,
Allocation: int(*g.Memory.AllocationMb),
Minimum: int(*g.Memory.MinimumMb),
Maximum: int(*g.Memory.MaximumMb),
StepSize: int(*g.Memory.StepSizeMb),
IsAdjustable: *g.Memory.IsAdjustable,
IsOptional: *g.Memory.IsOptional,
CanScaleDown: *g.Memory.CanScaleDown,
Units: *g.Memory.Units,
Allocation: int(*g.Memory.AllocationMb),
Minimum: int(*g.Memory.MinimumMb),
Maximum: int(*g.Memory.MaximumMb),
StepSize: int(*g.Memory.StepSizeMb),
IsAdjustable: *g.Memory.IsAdjustable,
IsOptional: *g.Memory.IsOptional,
CanScaleDown: *g.Memory.CanScaleDown,
CPUEnforcementRatioCeilingMb: getCPUEnforcementRatioCeilingMb(g.Memory),
CPUEnforcementRatioMb: getCPUEnforcementRatioMb(g.Memory),
}

group.Disk = &GroupResource{
Expand Down Expand Up @@ -2745,6 +2762,22 @@ func normalizeGroups(_groups []clouddatabasesv5.Group) (groups []Group) {
return groups
}

func getCPUEnforcementRatioCeilingMb(groupMemory *clouddatabasesv5.GroupMemory) int {
if groupMemory.CPUEnforcementRatioCeilingMb != nil {
return int(*groupMemory.CPUEnforcementRatioCeilingMb)
}

return 0
}

func getCPUEnforcementRatioMb(groupMemory *clouddatabasesv5.GroupMemory) int {
if groupMemory.CPUEnforcementRatioMb != nil {
return int(*groupMemory.CPUEnforcementRatioMb)
}

return 0
}

func expandGroups(_groups []interface{}) []*Group {
if len(_groups) == 0 {
return nil
Expand Down Expand Up @@ -2837,6 +2870,33 @@ func validateGroupHostFlavor(groupId string, resourceName string, group *Group)
return nil
}

func validateMultitenantMemoryCpu(resourceDefaults *Group, group *Group, cpuEnforcementRatioCeiling int, cpuEnforcementRatioMb int) error {
// TODO: Replace this with cpuEnforcementRatioCeiling when it is fixed
cpuEnforcementRatioCeilingTemp := 16384

if resourceDefaults.Members == nil {
return nil
}

if group.Memory.Allocation < resourceDefaults.Memory.Minimum/resourceDefaults.Members.Allocation {
return fmt.Errorf("We were unable to complete your request: group.memory requires a minimum of %d megabytes. Try again with valid values or contact support if the issue persists.", resourceDefaults.Memory.Minimum/resourceDefaults.Members.Allocation)
}

if group.CPU == nil {
return nil
}

if group.CPU != nil && group.CPU.Allocation > 2 {
Copy link
Collaborator

@obai-1 obai-1 Mar 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be something like group.CPU.Allocation >= EnforcementRatioCeiling/EnforcementRatio

return nil
}

if group.CPU != nil && group.Memory.Allocation*resourceDefaults.Members.Allocation < cpuEnforcementRatioCeilingTemp*resourceDefaults.Members.Allocation && group.Memory.Allocation*resourceDefaults.Members.Allocation > group.CPU.Allocation*cpuEnforcementRatioMb {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this validation useful, since you are not going to have any fractional values in group.CPU?

return fmt.Errorf("We were unable to complete your request: group.memory %d with group.cpu %d is not valid. Try again with valid values or contact support if the issue persists.", group.Memory.Allocation, group.CPU.Allocation)
}

return nil
}

func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) {
instanceID := diff.Id()
service := diff.Get("service").(string)
Expand All @@ -2846,11 +2906,23 @@ func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta inter
var currentGroups []Group
var groupList []clouddatabasesv5.Group
var groupIds []string
groups := expandGroups(group.(*schema.Set).List())
var memberGroup *Group
for _, g := range groups {
if g.ID == "member" {
memberGroup = g
break
}
}

if instanceID != "" {
groupList, err = getGroups(instanceID, meta)
} else {
groupList, err = getDefaultScalingGroups(service, plan, meta)
if memberGroup.HostFlavor != nil {
groupList, err = getDefaultScalingGroups(service, plan, memberGroup.HostFlavor.ID, meta)
} else {
groupList, err = getDefaultScalingGroups(service, plan, "", meta)
}
}

if err != nil {
Expand All @@ -2861,6 +2933,8 @@ func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta inter

tfGroups := expandGroups(group.(*schema.Set).List())

cpuEnforcementRatioCeiling, cpuEnforcementRatioMb := getCpuEnforcementRatios(service, plan, meta, group)

// validate group_ids are unique
groupIds = make([]string, 0, len(tfGroups))
for _, g := range tfGroups {
Expand Down Expand Up @@ -2892,15 +2966,15 @@ func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta inter
// set current nodeCount
nodeCount := groupDefaults.Members.Allocation

if group.Members != nil {
err = validateGroupScaling(groupId, "members", group.Members.Allocation, groupDefaults.Members, 1)
if group.HostFlavor != nil && group.HostFlavor.ID != "" && group.HostFlavor.ID != "multitenant" {
err = validateGroupHostFlavor(groupId, "host_flavor", group)
if err != nil {
return err
}
}

if group.HostFlavor != nil && group.HostFlavor.ID != "" && group.HostFlavor.ID != "multitenant" {
err = validateGroupHostFlavor(groupId, "host_flavor", group)
if group.Members != nil {
err = validateGroupScaling(groupId, "members", group.Members.Allocation, groupDefaults.Members, 1)
if err != nil {
return err
}
Expand All @@ -2911,6 +2985,13 @@ func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta inter
if err != nil {
return err
}

if group.HostFlavor != nil && group.HostFlavor.ID != "" && group.HostFlavor.ID == "multitenant" && cpuEnforcementRatioCeiling != 0 && cpuEnforcementRatioMb != 0 {
err = validateMultitenantMemoryCpu(groupDefaults, group, cpuEnforcementRatioCeiling, cpuEnforcementRatioMb)
if err != nil {
return err
}
}
}

if group.Disk != nil {
Expand All @@ -2932,6 +3013,41 @@ func validateGroupsDiff(_ context.Context, diff *schema.ResourceDiff, meta inter
return nil
}

func getCpuEnforcementRatios(service string, plan string, meta interface{}, group interface{}) (cpuEnforcementRatioCeiling int, cpuEnforcementRatioMb int) {
var currentGroups []Group
defaultList, err := getDefaultScalingGroups(service, plan, "multitenant", meta)

if err != nil {
return 0, 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to propagate the error upward from here, rather than returning 0, 0.

Something like return err, 0, 0

}

currentGroups = normalizeGroups(defaultList)
tfGroups := expandGroups(group.(*schema.Set).List())

// Get default or current group scaling values
for _, group := range tfGroups {
if group == nil {
break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would continue be better than break here?

}

groupId := group.ID
var groupDefaults *Group
for _, g := range currentGroups {
if g.ID == groupId {
groupDefaults = &g
break
}
}

if groupDefaults.Memory != nil {
return groupDefaults.Memory.CPUEnforcementRatioCeilingMb, groupDefaults.Memory.CPUEnforcementRatioMb
}

}

return 0, 0
}

func validateUsersDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) {
service := diff.Get("service").(string)

Expand Down Expand Up @@ -2972,7 +3088,7 @@ func validateUsersDiff(_ context.Context, diff *schema.ResourceDiff, meta interf

// TODO: Use Capability API
// RBAC roles supported for Redis 6.0 and above
if service == "databases-for-redis" && !(version > 0 && version < 6) {
if (service == "databases-for-redis") && !(version > 0 && version < 6) {
err = change.New.ValidateRBACRole()
} else if service == "databases-for-mongodb" && change.New.Type == "ops_manager" {
err = change.New.ValidateOpsManagerRole()
Expand Down
Loading
Loading