Files
kopia/internal/server/api_sources.go
Jarek Kowalski c9c8d27c8d Repro and fix for zero-sized snapshot bug (#641)
* server: repro for zero-sized snapshot bug

As described in https://kopia.discourse.group/t/kopia-0-7-0-not-backing-up-any-files-repro-needed/136/5

* server: fixed zero-sized snapshots after repository is connected via API

The root cause was that source manager was inheriting HTTP call context
which was immediately closed after the 'connect' RPC returned thus
silently killing all uploads.
2020-09-23 20:15:36 -07:00

125 lines
3.2 KiB
Go

package server
import (
"context"
"encoding/json"
"net/http"
"os"
"sort"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
func (s *Server) handleSourcesList(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
_, multiUser := s.rep.(*repo.DirectRepository)
resp := &serverapi.SourcesResponse{
Sources: []*serverapi.SourceStatus{},
LocalHost: s.rep.ClientOptions().Hostname,
LocalUsername: s.rep.ClientOptions().Username,
MultiUser: multiUser,
}
for _, v := range s.sourceManagers {
if !sourceMatchesURLFilter(v.src, r.URL.Query()) {
continue
}
resp.Sources = append(resp.Sources, v.Status())
}
sort.Slice(resp.Sources, func(i, j int) bool {
return resp.Sources[i].Source.String() < resp.Sources[j].Source.String()
})
return resp, nil
}
func (s *Server) handleSourcesCreate(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
var req serverapi.CreateSnapshotSourceRequest
if err := json.Unmarshal(body, &req); err != nil {
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request body")
}
if req.Path == "" {
return nil, requestError(serverapi.ErrorMalformedRequest, "missing path")
}
_, err := os.Stat(req.Path)
if os.IsNotExist(err) {
return nil, requestError(serverapi.ErrorPathNotFound, "path does not exist")
}
if err != nil {
return nil, internalServerError(err)
}
sourceInfo := snapshot.SourceInfo{
UserName: s.rep.ClientOptions().Username,
Host: s.rep.ClientOptions().Hostname,
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(ctx).Debugf("policy for %v already exists", sourceInfo)
resp.Created = false
case policy.ErrPolicyNotFound:
resp.Created = true
// don't have policy - create an empty one
log(ctx).Debugf("policy for %v not found, creating empty one", sourceInfo)
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 {
return nil, internalServerError(errors.Wrap(err, "unable to flush"))
}
default:
return nil, internalServerError(err)
}
// upgrade to exclusive lock to ensure we have source manager
s.mu.RUnlock()
s.mu.Lock()
if s.sourceManagers[sourceInfo] == nil {
log(ctx).Debugf("creating source manager for %v", sourceInfo)
sm := newSourceManager(sourceInfo, s)
s.sourceManagers[sourceInfo] = sm
go sm.run(ctx)
}
s.mu.Unlock()
s.mu.RLock()
manager := s.sourceManagers[sourceInfo]
if manager == nil {
return nil, internalServerError(errors.Errorf("could not find source manager that was just created"))
}
if req.CreateSnapshot {
resp.SnapshotStarted = true
log(ctx).Debugf("scheduling snapshot of %v immediately...", sourceInfo)
manager.scheduleSnapshotNow()
}
return resp, nil
}