diff --git a/internal/server/api_sources.go b/internal/server/api_sources.go index 63dbfd3af..c1a112d1e 100644 --- a/internal/server/api_sources.go +++ b/internal/server/api_sources.go @@ -60,20 +60,25 @@ func (s *Server) handleSourcesCreate(ctx context.Context, r *http.Request) (inte Path: req.Path, } + resp := &serverapi.CreateSnapshotSourceResponse{} + // ensure we have the policy for this source, otherwise it will not show up in the // list of sources at all. _, err = policy.GetDefinedPolicy(ctx, s.rep, sourceInfo) switch err { case nil: + // already have policy, do nothing log.Debugf("policy for %v already exists", sourceInfo) - // have policy, do nothing + + resp.Created = false case policy.ErrPolicyNotFound: + resp.Created = true // don't have policy - create an empty one log.Debugf("policy for %v not found, creating empty one", sourceInfo) - if err = policy.SetPolicy(ctx, s.rep, sourceInfo, &policy.Policy{}); err != nil { - return nil, internalServerError(errors.Wrap(err, "unable to set policy")) + if err = policy.SetPolicy(ctx, s.rep, sourceInfo, &req.InitialPolicy); err != nil { + return nil, internalServerError(errors.Wrap(err, "unable to set initial policy")) } if err = s.rep.Flush(ctx); err != nil { @@ -103,9 +108,11 @@ func (s *Server) handleSourcesCreate(ctx context.Context, r *http.Request) (inte } if req.CreateSnapshot { + resp.SnapshotStarted = true + log.Debugf("scheduling snapshot of %v immediately...", sourceInfo) manager.scheduleSnapshotNow() } - return &serverapi.Empty{}, nil + return resp, nil } diff --git a/internal/serverapi/client_wrappers.go b/internal/serverapi/client_wrappers.go index 2594806a7..229a24e2e 100644 --- a/internal/serverapi/client_wrappers.go +++ b/internal/serverapi/client_wrappers.go @@ -8,8 +8,13 @@ ) // CreateSnapshotSource creates snapshot source with a given path. -func (c *Client) CreateSnapshotSource(ctx context.Context, req *CreateSnapshotSourceRequest) error { - return c.Post("sources", req, &CreateSnapshotSourceRequest{}) +func (c *Client) CreateSnapshotSource(ctx context.Context, req *CreateSnapshotSourceRequest) (*CreateSnapshotSourceResponse, error) { + resp := &CreateSnapshotSourceResponse{} + if err := c.Post("sources", req, resp); err != nil { + return nil, err + } + + return resp, nil } // UploadSnapshots triggers snapshot upload on matching snapshots. @@ -72,7 +77,7 @@ func (c *Client) ListSources(ctx context.Context, match *snapshot.SourceInfo) (* return resp, nil } -// ListSnapshots invokes the 'sources' API. +// ListSnapshots lists the snapshots managed by the server for a given source filter. func (c *Client) ListSnapshots(ctx context.Context, match *snapshot.SourceInfo) (*SnapshotsResponse, error) { resp := &SnapshotsResponse{} if err := c.Get("snapshots"+matchSourceParameters(match), resp); err != nil { @@ -82,6 +87,16 @@ func (c *Client) ListSnapshots(ctx context.Context, match *snapshot.SourceInfo) return resp, nil } +// ListPolicies lists the policies managed by the server for a given target filter. +func (c *Client) ListPolicies(ctx context.Context, match *snapshot.SourceInfo) (*PoliciesResponse, error) { + resp := &PoliciesResponse{} + if err := c.Get("policies"+matchSourceParameters(match), resp); err != nil { + return nil, err + } + + return resp, nil +} + func matchSourceParameters(match *snapshot.SourceInfo) string { if match == nil { return "" diff --git a/internal/serverapi/serverapi.go b/internal/serverapi/serverapi.go index c73c069a1..6bb823205 100644 --- a/internal/serverapi/serverapi.go +++ b/internal/serverapi/serverapi.go @@ -116,8 +116,15 @@ type SupportedAlgorithmsResponse struct { // CreateSnapshotSourceRequest contains request to create snapshot source and optionally create first snapshot. type CreateSnapshotSourceRequest struct { - Path string `json:"path"` - CreateSnapshot bool `json:"createSnapshot"` + Path string `json:"path"` + CreateSnapshot bool `json:"createSnapshot"` + InitialPolicy policy.Policy `json:"initialPolicy"` // policy to set on the source when first created, ignored if already exists +} + +// CreateSnapshotSourceResponse contains response of creating snapshot source. +type CreateSnapshotSourceResponse struct { + Created bool `json:"created"` // whether the source was created (false==previously existed) + SnapshotStarted bool `json:"snapshotted"` // whether snapshotting has been started } // Snapshot describes single snapshot entry. diff --git a/tests/end_to_end_test/server_start_test.go b/tests/end_to_end_test/server_start_test.go index 77ca948a6..7336a7bb6 100644 --- a/tests/end_to_end_test/server_start_test.go +++ b/tests/end_to_end_test/server_start_test.go @@ -13,6 +13,7 @@ "github.com/kopia/kopia/repo/blob" "github.com/kopia/kopia/repo/blob/filesystem" "github.com/kopia/kopia/snapshot" + "github.com/kopia/kopia/snapshot/policy" "github.com/kopia/kopia/tests/testenv" ) @@ -97,12 +98,22 @@ func TestServerStart(t *testing.T) { t.Errorf("unexpected source path: %v, want %v", got, want) } - if err = cli.CreateSnapshotSource(ctx, &serverapi.CreateSnapshotSourceRequest{ + createResp, err := cli.CreateSnapshotSource(ctx, &serverapi.CreateSnapshotSourceRequest{ Path: sharedTestDataDir2, - }); err != nil { + }) + + if err != nil { t.Fatalf("create snapshot source error: %v", err) } + if !createResp.Created { + t.Errorf("unexpected value of 'created': %v", createResp.Created) + } + + if createResp.SnapshotStarted { + t.Errorf("unexpected value of 'snapshotStarted': %v", createResp.SnapshotStarted) + } + verifySourceCount(t, cli, nil, 2) verifySourceCount(t, cli, &snapshot.SourceInfo{Host: "no-such-host"}, 0) verifySourceCount(t, cli, &snapshot.SourceInfo{Path: sharedTestDataDir2}, 1) @@ -120,6 +131,41 @@ func TestServerStart(t *testing.T) { } verifySnapshotCount(t, cli, &snapshot.SourceInfo{Path: sharedTestDataDir2}, 1) + + keepDaily := 77 + + createResp, err = cli.CreateSnapshotSource(ctx, &serverapi.CreateSnapshotSourceRequest{ + Path: sharedTestDataDir3, + CreateSnapshot: true, + InitialPolicy: policy.Policy{ + RetentionPolicy: policy.RetentionPolicy{ + KeepDaily: &keepDaily, + }, + }, + }) + + if err != nil { + t.Fatalf("unable to create source") + } + + if !createResp.SnapshotStarted { + t.Errorf("unexpected value of 'snapshotStarted': %v", createResp.SnapshotStarted) + } + + policies, err := cli.ListPolicies(ctx, &snapshot.SourceInfo{Path: sharedTestDataDir3}) + if err != nil { + t.Errorf("aaa") + } + + if len(policies.Policies) != 1 { + t.Fatalf("unexpected number of policies") + } + + if got, want := *policies.Policies[0].Policy.RetentionPolicy.KeepDaily, keepDaily; got != want { + t.Errorf("initial policy not persisted") + } + + waitForSnapshotCount(t, cli, &snapshot.SourceInfo{Path: sharedTestDataDir3}, 1) } func TestServerStartWithoutInitialRepository(t *testing.T) {