Skip to content
Merged
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
34 changes: 29 additions & 5 deletions planetscale/deploy_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type DeployRequestsService interface {
CreateReview(context.Context, *ReviewDeployRequestRequest) (*DeployRequestReview, error)
Deploy(context.Context, *PerformDeployRequest) (*DeployRequest, error)
Diff(ctx context.Context, diffReq *DiffRequest) ([]*Diff, error)
ForceCutover(context.Context, *ForceCutoverDeployRequestRequest) (*DeployRequest, error)
Get(context.Context, *GetDeployRequestRequest) (*DeployRequest, error)
List(context.Context, *ListDeployRequestsRequest) ([]*DeployRequest, error)
GetDeployOperations(context.Context, *GetDeployOperationsRequest) ([]*DeployOperation, error)
Expand Down Expand Up @@ -138,11 +139,12 @@ type Deployment struct {
CutoverActor *Actor `json:"cutover_actor"`
CancelledActor *Actor `json:"cancelled_actor"`

CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
StartedAt *time.Time `json:"started_at"`
QueuedAt *time.Time `json:"queued_at"`
FinishedAt *time.Time `json:"finished_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
StartedAt *time.Time `json:"started_at"`
QueuedAt *time.Time `json:"queued_at"`
FinishedAt *time.Time `json:"finished_at"`
ForceCutoverRequestedAt *time.Time `json:"force_cutover_requested_at"`
}

// DeployRequest encapsulates the request to deploy a database branch's schema
Expand Down Expand Up @@ -182,6 +184,12 @@ type ApplyDeployRequestRequest struct {
Number uint64 `json:"-"`
}

type ForceCutoverDeployRequestRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
}

type AutoApplyDeployRequestRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Expand Down Expand Up @@ -366,6 +374,22 @@ func (d *deployRequestsService) ApplyDeploy(ctx context.Context, applyReq *Apply
return drr, nil
}

// ForceCutover requests a force cutover for a deploy request stuck in the cutover phase.
func (d *deployRequestsService) ForceCutover(ctx context.Context, forceReq *ForceCutoverDeployRequestRequest) (*DeployRequest, error) {
path := deployRequestActionAPIPath(forceReq.Organization, forceReq.Database, forceReq.Number, "force-cutover")
req, err := d.client.newRequest(http.MethodPost, path, forceReq)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

drr := &DeployRequest{}
if err := d.client.do(ctx, req, &drr); err != nil {
return nil, err
}

return drr, nil
}

func (d *deployRequestsService) AutoApplyDeploy(ctx context.Context, autoApplyReq *AutoApplyDeployRequestRequest) (*DeployRequest, error) {
reqBody := struct {
Enable bool `json:"enable"`
Expand Down
44 changes: 44 additions & 0 deletions planetscale/deploy_requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,50 @@ func TestDeployRequests_CancelDeploy(t *testing.T) {
c.Assert(dr, qt.DeepEquals, want)
}

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

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodPost)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/test-organization/databases/test-database/deploy-requests/1337/force-cutover")
w.WriteHeader(200)
out := `{"id": "test-deploy-request-id", "branch": "development", "into_branch": "some-branch", "notes": "", "created_at": "2021-01-14T10:19:23.000Z", "updated_at": "2021-01-14T10:19:23.000Z", "closed_at": null, "deployment": { "state": "in_progress_cutover", "force_cutover_requested_at": "2021-01-14T10:19:23.000Z" }, "number": 1337}`
_, err := w.Write([]byte(out))
c.Assert(err, qt.IsNil)
}))

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

ctx := context.Background()

dr, err := client.DeployRequests.ForceCutover(ctx, &ForceCutoverDeployRequestRequest{
Organization: "test-organization",
Database: "test-database",
Number: 1337,
})

testTime := time.Date(2021, time.January, 14, 10, 19, 23, 0, time.UTC)

want := &DeployRequest{
ID: "test-deploy-request-id",
Branch: "development",
Deployment: &Deployment{
State: "in_progress_cutover",
ForceCutoverRequestedAt: &testTime,
},
IntoBranch: "some-branch",
Number: 1337,
Notes: "",
CreatedAt: testTime,
UpdatedAt: testTime,
ClosedAt: nil,
}

c.Assert(err, qt.IsNil)
c.Assert(dr, qt.DeepEquals, want)
}

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

Expand Down
18 changes: 18 additions & 0 deletions planetscale/keyspaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ type GetKeyspaceRequest struct {
Full bool `json:"-"`
}

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

type UpdateReadOnlyRegionsRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Expand Down Expand Up @@ -184,6 +191,7 @@ type KeyspacesService interface {
Create(context.Context, *CreateKeyspaceRequest) (*Keyspace, error)
List(context.Context, *ListKeyspacesRequest) ([]*Keyspace, error)
Get(context.Context, *GetKeyspaceRequest) (*Keyspace, error)
Delete(context.Context, *DeleteKeyspaceRequest) error
UpdateReadOnlyRegions(context.Context, *UpdateReadOnlyRegionsRequest) ([]*ReadOnlyRegionKeyspace, error)
VSchema(context.Context, *GetKeyspaceVSchemaRequest) (*VSchema, error)
UpdateVSchema(context.Context, *UpdateKeyspaceVSchemaRequest) (*VSchema, error)
Expand Down Expand Up @@ -270,6 +278,16 @@ func (s *keyspacesService) Create(ctx context.Context, createReq *CreateKeyspace
return keyspace, nil
}

// Delete deletes a keyspace from a branch.
func (s *keyspacesService) Delete(ctx context.Context, deleteReq *DeleteKeyspaceRequest) error {
req, err := s.client.newRequest(http.MethodDelete, keyspaceAPIPath(deleteReq.Organization, deleteReq.Database, deleteReq.Branch, deleteReq.Keyspace), nil)
if err != nil {
return fmt.Errorf("error creating http request: %w", err)
}

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

// VSchema returns the VSchema for a keyspace in a branch
func (s *keyspacesService) VSchema(ctx context.Context, getReq *GetKeyspaceVSchemaRequest) (*VSchema, error) {
pathStr := path.Join(keyspaceAPIPath(getReq.Organization, getReq.Database, getReq.Branch, getReq.Keyspace), "vschema")
Expand Down
21 changes: 21 additions & 0 deletions planetscale/keyspaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,27 @@ func TestKeyspaces_Create(t *testing.T) {
c.Assert(keyspace.Shards, qt.Equals, 2)
}

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

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodDelete)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/foo/databases/bar/branches/baz/keyspaces/qux")
w.WriteHeader(http.StatusNoContent)
}))

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

err = client.Keyspaces.Delete(context.Background(), &DeleteKeyspaceRequest{
Organization: "foo",
Database: "bar",
Branch: "baz",
Keyspace: "qux",
})
c.Assert(err, qt.IsNil)
}

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

Expand Down
57 changes: 57 additions & 0 deletions planetscale/vtctld_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type VtctldService interface {
ListWorkflows(context.Context, *VtctldListWorkflowsRequest) (json.RawMessage, error)
ListKeyspaces(context.Context, *VtctldListKeyspacesRequest) (json.RawMessage, error)
GetRoutingRules(context.Context, *VtctldGetRoutingRulesRequest) (json.RawMessage, error)
GetKeyspaceRoutingRules(context.Context, *VtctldGetKeyspaceRoutingRulesRequest) (json.RawMessage, error)
ApplyKeyspaceRoutingRules(context.Context, *VtctldApplyKeyspaceRoutingRulesRequest) (json.RawMessage, error)
GetShard(context.Context, *VtctldGetShardRequest) (json.RawMessage, error)
SetShardTabletControl(context.Context, *VtctldSetShardTabletControlRequest) (json.RawMessage, error)
RefreshStateByShard(context.Context, *VtctldRefreshStateByShardRequest) (json.RawMessage, error)
Expand Down Expand Up @@ -52,6 +54,31 @@ type VtctldGetRoutingRulesRequest struct {
Branch string `json:"-"`
}

type VtctldKeyspaceRoutingRule struct {
FromKeyspace string `json:"from_keyspace"`
ToKeyspace string `json:"to_keyspace"`
}

type VtctldKeyspaceRoutingRules struct {
Rules []VtctldKeyspaceRoutingRule `json:"rules"`
}

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

type VtctldApplyKeyspaceRoutingRulesRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Branch string `json:"-"`

Rules []VtctldKeyspaceRoutingRule `json:"rules"`
SkipRebuild bool `json:"skip_rebuild"`
RebuildCells []string `json:"rebuild_cells,omitempty"`
}

// VtctldGetShardRequest is a request for reading a shard record from the
// cluster via vtctld.
type VtctldGetShardRequest struct {
Expand Down Expand Up @@ -197,6 +224,10 @@ func vtctldRoutingRulesAPIPath(org, db, branch string) string {
return path.Join(databaseBranchAPIPath(org, db, branch), "vtctld", "routing-rules")
}

func vtctldKeyspaceRoutingRulesAPIPath(org, db, branch string) string {
return path.Join(databaseBranchAPIPath(org, db, branch), "vtctld", "keyspace-routing-rules")
}

func vtctldShardAPIPath(org, db, branch string) string {
return path.Join(databaseBranchAPIPath(org, db, branch), "vtctld", "shard")
}
Expand Down Expand Up @@ -260,6 +291,32 @@ func (s *vtctldService) GetRoutingRules(ctx context.Context, req *VtctldGetRouti
return resp.Data, nil
}

func (s *vtctldService) GetKeyspaceRoutingRules(ctx context.Context, req *VtctldGetKeyspaceRoutingRulesRequest) (json.RawMessage, error) {
p := vtctldKeyspaceRoutingRulesAPIPath(req.Organization, req.Database, req.Branch)
httpReq, err := s.client.newRequest(http.MethodGet, p, nil)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}
resp := &vtctldDataResponse{}
if err := s.client.do(ctx, httpReq, resp); err != nil {
return nil, err
}
return resp.Data, nil
}

func (s *vtctldService) ApplyKeyspaceRoutingRules(ctx context.Context, req *VtctldApplyKeyspaceRoutingRulesRequest) (json.RawMessage, error) {
p := vtctldKeyspaceRoutingRulesAPIPath(req.Organization, req.Database, req.Branch)
httpReq, err := s.client.newRequest(http.MethodPut, p, req)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}
resp := &vtctldDataResponse{}
if err := s.client.do(ctx, httpReq, resp); err != nil {
return nil, err
}
return resp.Data, nil
}

func (s *vtctldService) GetShard(ctx context.Context, req *VtctldGetShardRequest) (json.RawMessage, error) {
p := vtctldShardAPIPath(req.Organization, req.Database, req.Branch)
v := url.Values{}
Expand Down
66 changes: 66 additions & 0 deletions planetscale/vtctld_general_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,72 @@ func TestVtctld_GetRoutingRules(t *testing.T) {
c.Assert(string(data), qt.Equals, `{"rules":[]}`)
}

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

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodGet)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/my-org/databases/my-db/branches/my-branch/vtctld/keyspace-routing-rules")

w.WriteHeader(200)
_, err := w.Write([]byte(`{"data":{"rules":[]}}`))
c.Assert(err, qt.IsNil)
}))
defer ts.Close()

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

data, err := client.Vtctld.GetKeyspaceRoutingRules(context.Background(), &VtctldGetKeyspaceRoutingRulesRequest{
Organization: "my-org",
Database: "my-db",
Branch: "my-branch",
})
c.Assert(err, qt.IsNil)
c.Assert(string(data), qt.Equals, `{"rules":[]}`)
}

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

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Assert(r.Method, qt.Equals, http.MethodPut)
c.Assert(r.URL.Path, qt.Equals, "/v1/organizations/my-org/databases/my-db/branches/my-branch/vtctld/keyspace-routing-rules")

var body VtctldApplyKeyspaceRoutingRulesRequest
err := json.NewDecoder(r.Body).Decode(&body)
c.Assert(err, qt.IsNil)
c.Assert(body.Rules, qt.DeepEquals, []VtctldKeyspaceRoutingRule{{
FromKeyspace: "source",
ToKeyspace: "target",
}})
c.Assert(body.SkipRebuild, qt.IsTrue)
c.Assert(body.RebuildCells, qt.DeepEquals, []string{"zone1"})

w.WriteHeader(200)
_, err = w.Write([]byte(`{"data":{"rules":[{"from_keyspace":"source","to_keyspace":"target"}]}}`))
c.Assert(err, qt.IsNil)
}))
defer ts.Close()

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

data, err := client.Vtctld.ApplyKeyspaceRoutingRules(context.Background(), &VtctldApplyKeyspaceRoutingRulesRequest{
Organization: "my-org",
Database: "my-db",
Branch: "my-branch",
Rules: []VtctldKeyspaceRoutingRule{{
FromKeyspace: "source",
ToKeyspace: "target",
}},
SkipRebuild: true,
RebuildCells: []string{"zone1"},
})
c.Assert(err, qt.IsNil)
c.Assert(string(data), qt.Equals, `{"rules":[{"from_keyspace":"source","to_keyspace":"target"}]}`)
}

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

Expand Down
Loading