Skip to content

Commit

Permalink
Add keyspace resize methods (#216)
Browse files Browse the repository at this point in the history
* Add Resize method

* Reorder tests

* Implement CancelResize

* Add ResizeStatus method to keyspace

* Add comment

* Change param from replicas to extra replicas

* Remove omit empty from extra replicas

* Make replicas and cluster size pointers on resize, so they can truly be optional
  • Loading branch information
iheanyi authored Oct 2, 2024
1 parent accc969 commit 27c24f0
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 0 deletions.
99 changes: 99 additions & 0 deletions planetscale/keyspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,57 @@ type branchKeyspacesResponse struct {
Keyspaces []*Keyspace `json:"data"`
}

type ResizeKeyspaceRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Branch string `json:"-"`
Keyspace string `json:"-"`
ExtraReplicas *uint `json:"extra_replicas,omitempty"`
ClusterSize *ClusterSize `json:"cluster_size,omitempty"`
}

type KeyspaceResizeRequest struct {
ID string `json:"id"`
State string `json:"state"`
Actor *Actor `json:"actor"`

ClusterSize ClusterSize `json:"cluster_rate_name"`
PreviousClusterSize ClusterSize `json:"previous_cluster_rate_name"`

Replicas uint `json:"replicas"`
ExtraReplicas uint `json:"extra_replicas"`
PreviousReplicas uint `json:"previous_replicas"`

UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
StartedAt *time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at"`
}

type CancelKeyspaceResizeRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Branch string `json:"-"`
Keyspace string `json:"-"`
}

type KeyspaceResizeStatusRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Branch string `json:"-"`
Keyspace string `json:"-"`
}

// BranchKeyspaceService is an interface for interacting with the keyspace endpoints of the PlanetScale API
type BranchKeyspacesService interface {
Create(context.Context, *CreateBranchKeyspaceRequest) (*Keyspace, error)
List(context.Context, *ListBranchKeyspacesRequest) ([]*Keyspace, error)
Get(context.Context, *GetBranchKeyspaceRequest) (*Keyspace, error)
VSchema(context.Context, *GetKeyspaceVSchemaRequest) (*VSchema, error)
UpdateVSchema(context.Context, *UpdateKeyspaceVSchemaRequest) (*VSchema, error)
Resize(context.Context, *ResizeKeyspaceRequest) (*KeyspaceResizeRequest, error)
CancelResize(context.Context, *CancelKeyspaceResizeRequest) error
ResizeStatus(context.Context, *KeyspaceResizeStatusRequest) (*KeyspaceResizeRequest, error)
}

type branchKeyspacesService struct {
Expand Down Expand Up @@ -167,10 +211,65 @@ func (s *branchKeyspacesService) UpdateVSchema(ctx context.Context, updateReq *U
return vschema, nil
}

// Resize starts or queues a resize of a branch's keyspace.
func (s *branchKeyspacesService) Resize(ctx context.Context, resizeReq *ResizeKeyspaceRequest) (*KeyspaceResizeRequest, error) {
req, err := s.client.newRequest(http.MethodPut, databaseBranchKeyspaceResizesAPIPath(resizeReq.Organization, resizeReq.Database, resizeReq.Branch, resizeReq.Keyspace), resizeReq)
if err != nil {
return nil, errors.Wrap(err, "error creating http request")
}

keyspaceResize := &KeyspaceResizeRequest{}
if err := s.client.do(ctx, req, keyspaceResize); err != nil {
return nil, err
}

return keyspaceResize, nil
}

// CancelResize cancels a queued resize of a branch's keyspace.
func (s *branchKeyspacesService) CancelResize(ctx context.Context, cancelReq *CancelKeyspaceResizeRequest) error {
req, err := s.client.newRequest(http.MethodDelete, databaseBranchKeyspaceResizesAPIPath(cancelReq.Organization, cancelReq.Database, cancelReq.Branch, cancelReq.Keyspace), nil)
if err != nil {
return errors.Wrap(err, "error creating http request")
}

return s.client.do(ctx, req, nil)
}

func databaseBranchKeyspacesAPIPath(org, db, branch string) string {
return fmt.Sprintf("%s/keyspaces", databaseBranchAPIPath(org, db, branch))
}

func databaseBranchKeyspaceAPIPath(org, db, branch, keyspace string) string {
return fmt.Sprintf("%s/%s", databaseBranchKeyspacesAPIPath(org, db, branch), keyspace)
}

func databaseBranchKeyspaceResizesAPIPath(org, db, branch, keyspace string) string {
return fmt.Sprintf("%s/resizes", databaseBranchKeyspaceAPIPath(org, db, branch, keyspace))
}

type keyspaceResizesResponse struct {
Resizes []*KeyspaceResizeRequest `json:"data"`
}

func (s *branchKeyspacesService) ResizeStatus(ctx context.Context, resizeReq *KeyspaceResizeStatusRequest) (*KeyspaceResizeRequest, error) {
req, err := s.client.newRequest(http.MethodGet, databaseBranchKeyspaceResizesAPIPath(resizeReq.Organization, resizeReq.Database, resizeReq.Branch, resizeReq.Keyspace), nil)
if err != nil {
return nil, errors.Wrap(err, "error creating http request")
}

resizesResponse := &keyspaceResizesResponse{}
if err := s.client.do(ctx, req, resizesResponse); err != nil {
return nil, err
}

// If there are no resizes, treat the same as a not found error
if len(resizesResponse.Resizes) == 0 {
return nil, &Error{
msg: "Not Found",
Code: ErrNotFound,
}
}

return resizesResponse.Resizes[0], nil
}
128 changes: 128 additions & 0 deletions planetscale/keyspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,131 @@ func TestKeyspaces_UpdateVSchema(t *testing.T) {
c.Assert(vSchema.Raw, qt.Equals, wantRaw)
c.Assert(vSchema.HTML, qt.Equals, wantHTML)
}

func TestKeyspaces_Resize(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
out := `{"id":"thisisanid","type":"KeyspaceResizeRequest","state":"pending","started_at":"2024-06-25T18:03:09.459Z","completed_at":"2024-06-25T18:04:06.228Z","created_at":"2024-06-25T18:03:09.439Z","updated_at":"2024-06-25T18:04:06.238Z","actor":{"id":"actorid","type":"User","display_name":"Test User"},"cluster_rate_name":"PS_10","extra_replicas":1,"previous_cluster_rate_name":"PS_10","replicas":3,"previous_replicas":5}`
_, err := w.Write([]byte(out))
c.Assert(err, qt.IsNil)
c.Assert(r.Method, qt.Equals, http.MethodPut)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

ctx := context.Background()

size := ClusterSize("PS_10")
replicas := uint(3)

krr, err := client.Keyspaces.Resize(ctx, &ResizeKeyspaceRequest{
Organization: "foo",
Database: "bar",
Branch: "baz",
Keyspace: "qux",
ClusterSize: &size,
ExtraReplicas: &replicas,
})

wantID := "thisisanid"

c.Assert(err, qt.IsNil)
c.Assert(krr.ID, qt.Equals, wantID)
c.Assert(krr.ExtraReplicas, qt.Equals, uint(1))
c.Assert(krr.Replicas, qt.Equals, uint(3))
c.Assert(krr.PreviousReplicas, qt.Equals, uint(5))
c.Assert(krr.ClusterSize, qt.Equals, ClusterSize("PS_10"))
c.Assert(krr.PreviousClusterSize, qt.Equals, ClusterSize("PS_10"))
}

func TestKeyspaces_CancelResize(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
c.Assert(r.Method, qt.Equals, http.MethodDelete)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

ctx := context.Background()

err = client.Keyspaces.CancelResize(ctx, &CancelKeyspaceResizeRequest{
Organization: "foo",
Database: "bar",
Branch: "baz",
Keyspace: "qux",
})

c.Assert(err, qt.IsNil)
}

func TestKeyspaces_ResizeStatus(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
out := `{"type":"list","current_page":1,"next_page":null,"next_page_url":null,"prev_page":null,"prev_page_url":null,"data":[{"id":"thisisanid","type":"KeyspaceResizeRequest","state":"completed","started_at":"2024-06-25T18:03:09.459Z","completed_at":"2024-06-25T18:04:06.228Z","created_at":"2024-06-25T18:03:09.439Z","updated_at":"2024-06-25T18:04:06.238Z","actor":{"id":"thisisanid","type":"User","display_name":"Test User"},"cluster_rate_name":"PS_10","extra_replicas":0,"previous_cluster_rate_name":"PS_10","replicas":2,"previous_replicas":5}]}`
_, err := w.Write([]byte(out))
c.Assert(err, qt.IsNil)
c.Assert(r.Method, qt.Equals, http.MethodGet)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

ctx := context.Background()

krr, err := client.Keyspaces.ResizeStatus(ctx, &KeyspaceResizeStatusRequest{
Organization: "foo",
Database: "bar",
Branch: "baz",
Keyspace: "qux",
})

wantID := "thisisanid"

c.Assert(err, qt.IsNil)
c.Assert(krr.ID, qt.Equals, wantID)
c.Assert(krr.ExtraReplicas, qt.Equals, uint(0))
c.Assert(krr.Replicas, qt.Equals, uint(2))
c.Assert(krr.PreviousReplicas, qt.Equals, uint(5))
c.Assert(krr.ClusterSize, qt.Equals, ClusterSize("PS_10"))
c.Assert(krr.PreviousClusterSize, qt.Equals, ClusterSize("PS_10"))
}

func TestKeyspaces_ResizeStatusEmpty(t *testing.T) {
c := qt.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
out := `{"type":"list","current_page":1,"next_page":null,"next_page_url":null,"prev_page":null,"prev_page_url":null,"data":[]}`
_, err := w.Write([]byte(out))
c.Assert(err, qt.IsNil)
c.Assert(r.Method, qt.Equals, http.MethodGet)
}))

client, err := NewClient(WithBaseURL(ts.URL))
c.Assert(err, qt.IsNil)

ctx := context.Background()

krr, err := client.Keyspaces.ResizeStatus(ctx, &KeyspaceResizeStatusRequest{
Organization: "foo",
Database: "bar",
Branch: "baz",
Keyspace: "qux",
})

wantError := &Error{
msg: "Not Found",
Code: ErrNotFound,
}

c.Assert(krr, qt.IsNil)
c.Assert(err.Error(), qt.Equals, wantError.Error())
}

0 comments on commit 27c24f0

Please sign in to comment.