Skip to content

Suggest closest package when trying to create or scale a cluster - #235

Open
bashofmann wants to merge 1 commit into
mainfrom
feat/suggest-closest-packages
Open

bashofmann wants to merge 1 commit into
mainfrom
feat/suggest-closest-packages

Conversation

@bashofmann

Copy link
Copy Markdown
Contributor

When scaling or creating a cluster and the picked ressources do not match a package, a few closest packages are suggested.

When scaling or creating a cluster and the picked ressources do not match a package, a few closest packages are suggested.
@bashofmann
bashofmann requested a review from a team October 24, 2026 14:51
@bashofmann
bashofmann requested a review from a team as a code owner October 24, 2026 14:51

@AstraBert AstraBert left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm!

@Davidonium Davidonium left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The commit subject and PR title need to be Conventional Commits for releaser-pleaser (AGENTS.md). It's a single commit, so the subject is what lands on main. Suggested: feat(cluster): suggest closest packages when no package matches. Also a typo in the description and commit body: "ressources".

return matches[0], nil
case 0:
return nil, fmt.Errorf("no package found matching %s; use 'package list' to see available packages", desc)
suggestions := closestPackages(q, resp.GetItems(), maxPackageSuggestions)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When MultiAz is false, the request leaves req.MultiAz unset (line 175). booking.proto says unset means "all packages are returned", so a single-AZ create or scale gets multi-AZ packages in its suggestions. There's no multi-AZ column, so they show up as duplicate rows. Can we send req.MultiAz = new(q.MultiAz)? false excludes multi-AZ. Exact matching (line 175) and ResolvePackageByName (line 132) have the same gap. That's older code, but the suggestions table now makes it visible.

Also, for scale, the cluster's current package can show up as a suggestion. Should it be excluded?

// one dimension exactly ranks above one that is off in all of them.
// The next biggest package, the closest one that has at least the requested
// amount of every resource, always comes first.
func closestPackages(q PackageResourceQuery, pkgs []*bookingv1.Package, limit int) []packageSuggestion {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Packages also have a tier field. Standard and premium packages with the same CPU/RAM come out as identical rows in whatever order the API returns them. Either filter by tier or add a TIER column so they can be told apart.

var distance float64
missing := false
fits := true
for _, d := range []struct{ want, have int64 }{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Disk isn't part of the distance, so packages that differ only in disk always tie and fall back to API order. If that's intentional, adding disk to the tie-breakers in the sort at line 312 would at least make the order stable.

)
})

if i := slices.IndexFunc(suggestions, func(sg packageSuggestion) bool { return sg.fits }); i > 0 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"Next biggest" is the fitting package with the smallest log distance, not the smallest package that fits. Asking for 2 CPU / 8GiB with A = 2/64GiB and B = 4/16GiB picks B. Also, fits only looks at the flags passed, so create --cpu 4 treats every 4+ CPU package as fitting regardless of RAM. Either pick the smallest fitting package explicitly or rename the label to something like "closest larger package".


sb.WriteString("closest available packages:\n")

w := tabwriter.NewWriter(&sb, 0, 0, 2, ' ', 0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This adds a second table renderer next to output.NewTable. I see why: the table is indented inside an error string. Did you try output.NewTable with a writer that adds the prefix? Not blocking.

}

disk := sg.pkg.GetResourceConfiguration().GetDisk()
if parsed, err := resource.ParseByteQuantity(disk); err == nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Disk is normalised here (16GiB), but package list prints the raw value (16Gi). Same for CPU/RAM through .String(). The two commands should show a package's resources the same way. If this formatting stays, it could move to internal/cmd/output and package list could use it too.

)
require.Error(t, err)
msg := err.Error()
for _, name := range []string{"pkg-1 ", "pkg-2 ", "pkg-3 ", "pkg-4 ", "pkg-5 "} {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The trailing space in "pkg-1 " is what keeps it from matching pkg-10, which breaks easily. Asserting on whole table rows, or on the name followed by a tab or column gap, would be more reliable.

use 'package list' to see all available packages`, err.Error())
}

func TestCreateCluster_PackageByResourcesNoMatchNothingBigEnough(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No test covers the fallback where every package lacks a requested dimension and the old one-line error is returned (resolve.go:237-239). Can you add one? For example, request --gpu while every package has no GPU.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants