Skip to content
Open
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
7 changes: 7 additions & 0 deletions internal/cmd/keyspace/keyspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type KeyspaceSettings struct {
VReplicationFlags VReplicationFlags `header:"inline" json:"vreplication_flags"`
MaxRollout string `header:"max rollout" json:"max_rollout"`
Throttler Throttler `header:"inline" json:"throttler"`
Storage Storage `header:"inline" json:"storage"`

orig *ps.Keyspace
}
Expand All @@ -80,6 +81,12 @@ type Throttler struct {
Threshold string `header:"throttler threshold" json:"threshold"`
}

type Storage struct {
DiskScalingStrategy string `header:"disk scaling strategy" json:"disk_scaling_strategy"`
StorageBytes string `header:"storage" json:"storage_bytes"`
MaxStorageBytes string `header:"max storage" json:"max_storage_bytes"`
}

func toKeyspaces(keyspaces []*ps.Keyspace) []*Keyspace {
kss := make([]*Keyspace, 0, len(keyspaces))

Expand Down
21 changes: 20 additions & 1 deletion internal/cmd/keyspace/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strconv"

"github.com/dustin/go-humanize"
"github.com/planetscale/cli/internal/cmdutil"
ps "github.com/planetscale/cli/internal/planetscale"
"github.com/planetscale/cli/internal/printer"
Expand Down Expand Up @@ -55,7 +56,12 @@ func SettingsCmd(ch *cmdutil.Helper) *cobra.Command {
func toKeyspaceSettings(ks *ps.Keyspace) *KeyspaceSettings {
settings := &KeyspaceSettings{
MaxRollout: "not set",
orig: ks,
Storage: Storage{
DiskScalingStrategy: "not set",
StorageBytes: "not set",
MaxStorageBytes: "not set",
},
orig: ks,
}

if ks.MaxRollout != nil {
Expand Down Expand Up @@ -100,5 +106,18 @@ func toKeyspaceSettings(ks *ps.Keyspace) *KeyspaceSettings {
}
}

// Set the disk storage settings if available
if ks.Storage != nil {
if ks.Storage.DiskScalingStrategy != "" {
settings.Storage.DiskScalingStrategy = ks.Storage.DiskScalingStrategy
}
if ks.Storage.StorageBytes > 0 {
settings.Storage.StorageBytes = humanize.IBytes(uint64(ks.Storage.StorageBytes))
}
if ks.Storage.MaxStorageBytes > 0 {
settings.Storage.MaxStorageBytes = humanize.IBytes(uint64(ks.Storage.MaxStorageBytes))
}
}

return settings
}
26 changes: 26 additions & 0 deletions internal/cmd/keyspace/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,21 @@ func TestBuildKeyspaceSettings(t *testing.T) {

maxRollout := 8
fullKs.MaxRollout = &maxRollout
fullKs.Storage = &ps.KeyspaceStorage{
StorageBytes: 107374182400,
MaxStorageBytes: 4398046511104,
DiskScalingStrategy: "grow",
}

settings := toKeyspaceSettings(fullKs)
c.Assert(settings.ReplicationDurabilityConstraintStrategy, qt.Equals, "maximum") // Should be translated
c.Assert(settings.MaxRollout, qt.Equals, "8")
c.Assert(settings.VReplicationFlags.OptimizeInserts, qt.Equals, true)
c.Assert(settings.VReplicationFlags.AllowNoBlobBinlogRowImage, qt.Equals, true)
c.Assert(settings.VReplicationFlags.VPlayerBatching, qt.Equals, false)
c.Assert(settings.Storage.DiskScalingStrategy, qt.Equals, "grow")
c.Assert(settings.Storage.StorageBytes, qt.Equals, "100 GiB")
c.Assert(settings.Storage.MaxStorageBytes, qt.Equals, "4.0 TiB")

// Test with nil settings
nilKs := &ps.Keyspace{
Expand All @@ -226,4 +234,22 @@ func TestBuildKeyspaceSettings(t *testing.T) {
c.Assert(nilSettings.VReplicationFlags.OptimizeInserts, qt.Equals, false) // Default values
c.Assert(nilSettings.VReplicationFlags.AllowNoBlobBinlogRowImage, qt.Equals, false)
c.Assert(nilSettings.VReplicationFlags.VPlayerBatching, qt.Equals, false)
c.Assert(nilSettings.Storage.DiskScalingStrategy, qt.Equals, "not set")
c.Assert(nilSettings.Storage.StorageBytes, qt.Equals, "not set")
c.Assert(nilSettings.Storage.MaxStorageBytes, qt.Equals, "not set")

// Test with a storage object that only carries a strategy, which is what
// the API returns once autoscaling is disabled.
disabledKs := &ps.Keyspace{
ID: "ks1",
Name: "test",
Storage: &ps.KeyspaceStorage{
DiskScalingStrategy: "disable",
},
}

disabledSettings := toKeyspaceSettings(disabledKs)
c.Assert(disabledSettings.Storage.DiskScalingStrategy, qt.Equals, "disable")
c.Assert(disabledSettings.Storage.StorageBytes, qt.Equals, "not set")
c.Assert(disabledSettings.Storage.MaxStorageBytes, qt.Equals, "not set")
}
67 changes: 66 additions & 1 deletion internal/cmd/keyspace/update_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@ import (
"context"
"errors"
"fmt"
"slices"
"strconv"
"strings"

"github.com/charmbracelet/huh"
"github.com/dustin/go-humanize"
"github.com/planetscale/cli/internal/cmdutil"
ps "github.com/planetscale/cli/internal/planetscale"
"github.com/planetscale/cli/internal/printer"
"github.com/spf13/cobra"
)

// diskScalingStrategies are the disk scaling strategies accepted by the
// --disk-scaling-strategy flag.
var diskScalingStrategies = []string{"grow", "disable", "shrink"}

// shrinkStrategy recreates disks at the requested size and then disables
// autoscaling. It is the only strategy that accepts --storage.
const shrinkStrategy = "shrink"

func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
updateReq := &ps.UpdateKeyspaceSettingsRequest{}

Expand All @@ -22,6 +33,9 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
throttlerEnabled bool
throttlerThreshold float64
maxRollout int
diskScalingStrategy string
maxStorage int64
storage int64
interactive bool
}

Expand Down Expand Up @@ -56,7 +70,12 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {

maxRolloutChanged := cmd.Flags().Changed("max-rollout")

if !rdcChanged && !vrfChanged && !throttlerChanged && !maxRolloutChanged {
strategyChanged := cmd.Flags().Changed("disk-scaling-strategy")
maxStorageChanged := cmd.Flags().Changed("max-storage")
storageChanged := cmd.Flags().Changed("storage")
diskStorageChanged := strategyChanged || maxStorageChanged || storageChanged

if !rdcChanged && !vrfChanged && !throttlerChanged && !maxRolloutChanged && !diskStorageChanged {
ch.Printer.Println("No changes were requested. No update performed.")
return nil
}
Expand All @@ -69,6 +88,30 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
return errors.New("--max-rollout must be between 1 and 32")
}

if strategyChanged && !slices.Contains(diskScalingStrategies, flags.diskScalingStrategy) {
return fmt.Errorf("invalid --disk-scaling-strategy %q, must be one of: %s", flags.diskScalingStrategy, strings.Join(diskScalingStrategies, ", "))
}

if maxStorageChanged && flags.maxStorage <= 0 {
return errors.New("--max-storage must be greater than 0")
}

if storageChanged {
// The API only recreates disks at a new size when shrinking, so
// the strategy has to be part of the same request.
if !strategyChanged || flags.diskScalingStrategy != shrinkStrategy {
return fmt.Errorf("--storage can only be set when --disk-scaling-strategy is %s", shrinkStrategy)

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.

What if they previously shrank a disk? In that case the API has already stored shrink scaling strategy. Seems like they ought to be able to just update the storage. Would it simplify things if we didn't do this validation in the CLI and just relied on the API.

}

if flags.storage <= 0 {
return errors.New("--storage must be greater than 0")
}

if flags.storage%humanize.GiByte != 0 {
return errors.New("--storage must be a multiple of 1 GiB")
}
}

client, err := ch.Client()
if err != nil {
return err
Expand Down Expand Up @@ -127,6 +170,23 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
updateReq.MaxRollout = &flags.maxRollout
}

// Disk storage fields that are left out keep their current values.
if diskStorageChanged {
updateReq.Storage = &ps.KeyspaceStorageUpdate{}

if strategyChanged {
updateReq.Storage.DiskScalingStrategy = &flags.diskScalingStrategy
}

if maxStorageChanged {
updateReq.Storage.MaxStorageBytes = &flags.maxStorage
}

if storageChanged {
updateReq.Storage.StorageBytes = &flags.storage
}
}

k, err := updateKeyspaceSettings(ctx, client, updateReq)
if err != nil {
return err
Expand All @@ -145,8 +205,13 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
cmd.Flags().BoolVar(&flags.throttlerEnabled, "throttler-enabled", true, "Pause schema migrations and VReplication workflows when replication lag rises above the threshold.")
cmd.Flags().Float64Var(&flags.throttlerThreshold, "throttler-threshold", 5, "Replication lag in seconds above which migrations and workflows are paused.")
cmd.Flags().IntVar(&flags.maxRollout, "max-rollout", 1, "Maximum number of shards to roll out changes to concurrently (1-32).")
cmd.Flags().StringVar(&flags.diskScalingStrategy, "disk-scaling-strategy", "grow", fmt.Sprintf("The disk scaling strategy (%s). 'grow' lets dedicated disks grow automatically up to --max-storage; 'disable' turns autoscaling off; 'shrink' recreates disks at --storage and then disables autoscaling.", strings.Join(diskScalingStrategies, ", ")))
cmd.Flags().Int64Var(&flags.maxStorage, "max-storage", 0, "The maximum size in bytes that dedicated disks may autoscale to.")
cmd.Flags().Int64Var(&flags.storage, "storage", 0, fmt.Sprintf("The disk size in bytes to recreate disks at. Must be a multiple of 1 GiB and requires --disk-scaling-strategy %s.", shrinkStrategy))
cmd.Flags().BoolVarP(&flags.interactive, "interactive", "i", false, "Run the command in interactive mode")

_ = cmd.RegisterFlagCompletionFunc("disk-scaling-strategy", cobra.FixedCompletions(diskScalingStrategies, cobra.ShellCompDirectiveNoFileComp))

return cmd
}

Expand Down
Loading
Loading