func testEnumerate(t *testing.T, sto blobserver.Storage, wantUnsorted []blob.SizedRef, opts ...interface{}) { var after string var n = 1000 for _, opt := range opts { switch v := opt.(type) { case string: after = v case int: n = v default: panic("bad option of type " + fmt.Sprint("%T", v)) } } want := append([]blob.SizedRef(nil), wantUnsorted...) sort.Sort(blob.SizedByRef(want)) sbc := make(chan blob.SizedRef, 10) var got []blob.SizedRef var grp syncutil.Group sawEnd := make(chan bool, 1) grp.Go(func() error { if err := sto.EnumerateBlobs(context.New(), sbc, after, n); err != nil { return fmt.Errorf("EnumerateBlobs(%q, %d): %v", after, n) } return nil }) grp.Go(func() error { for sb := range sbc { if !sb.Valid() { return fmt.Errorf("invalid blobref %#v received in enumerate", sb) } got = append(got, sb) } sawEnd <- true return nil }) grp.Go(func() error { select { case <-sawEnd: return nil case <-time.After(10 * time.Second): return errors.New("timeout waiting for EnumerateBlobs to close its channel") } }) if err := grp.Err(); err != nil { t.Fatalf("Enumerate error: %v", err) return } if len(got) == 0 && len(want) == 0 { return } if !reflect.DeepEqual(got, want) { t.Fatalf("Enumerate mismatch. Got %d; want %d.\n Got: %v\nWant: %v\n", len(got), len(want), got, want) } }
func enumerateAllBlobs(s blobserver.Storage, destc chan<- blobref.SizedBlobRef) error { // Use *client.Client's support for enumerating all blobs if // possible, since it could probably do a better job knowing // HTTP boundaries and such. if nh, ok := s.(noHub); ok { return nh.Client.SimpleEnumerateBlobs(destc) } const batchSize = 1000 defer close(destc) after := "" for { var wg sync.WaitGroup wg.Add(1) ch := make(chan blobref.SizedBlobRef) n := 0 go func() { defer wg.Done() for sb := range ch { after = sb.BlobRef.String() destc <- sb n++ } }() if err := s.EnumerateBlobs(ch, after, batchSize, 0); err != nil { return err } wg.Wait() if n == 0 { return nil } } }
func (h *Handler) uploadPublicKey(sto blobserver.Storage, key string) error { _, err := blobserver.StatBlob(sto, h.pubKeyBlobRef) if err == nil { return nil } _, err = sto.ReceiveBlob(h.pubKeyBlobRef, strings.NewReader(key)) return err }
func handleRemove(conn http.ResponseWriter, req *http.Request, storage blobserver.Storage) { if w, ok := storage.(blobserver.ContextWrapper); ok { storage = w.WrapContext(req) } if req.Method != "POST" { log.Fatalf("Invalid method; handlers misconfigured") } configer, ok := storage.(blobserver.Configer) if !ok { conn.WriteHeader(http.StatusForbidden) fmt.Fprintf(conn, "Remove handler's blobserver.Storage isn't a blobserver.Configer; can't remove") return } if !configer.Config().IsQueue { conn.WriteHeader(http.StatusForbidden) fmt.Fprintf(conn, "Can only remove blobs from a queue.\n") return } n := 0 toRemove := make([]*blobref.BlobRef, 0) toRemoveStr := make([]string, 0) for { n++ if n > maxRemovesPerRequest { httputil.BadRequestError(conn, fmt.Sprintf("Too many removes in this request; max is %d", maxRemovesPerRequest)) return } key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { break } ref := blobref.Parse(value) if ref == nil { httputil.BadRequestError(conn, "Bogus blobref for key "+key) return } toRemove = append(toRemove, ref) toRemoveStr = append(toRemoveStr, ref.String()) } err := storage.RemoveBlobs(toRemove) if err != nil { conn.WriteHeader(http.StatusInternalServerError) log.Printf("Server error during remove: %v", err) fmt.Fprintf(conn, "Server error") return } reply := make(map[string]interface{}, 0) reply["removed"] = toRemoveStr httputil.ReturnJSON(conn, reply) }
func handleRemove(rw http.ResponseWriter, req *http.Request, storage blobserver.Storage) { if req.Method != "POST" { log.Fatalf("Invalid method; handlers misconfigured") } configer, ok := storage.(blobserver.Configer) if !ok { rw.WriteHeader(http.StatusForbidden) fmt.Fprintf(rw, "Remove handler's blobserver.Storage isn't a blobserver.Configer; can't remove") return } if !configer.Config().Deletable { rw.WriteHeader(http.StatusForbidden) fmt.Fprintf(rw, "storage does not permit deletes.\n") return } n := 0 toRemove := make([]blob.Ref, 0) for { n++ if n > maxRemovesPerRequest { httputil.BadRequestError(rw, fmt.Sprintf("Too many removes in this request; max is %d", maxRemovesPerRequest)) return } key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { break } ref, ok := blob.Parse(value) if !ok { httputil.BadRequestError(rw, "Bogus blobref for key "+key) return } toRemove = append(toRemove, ref) } err := storage.RemoveBlobs(toRemove) if err != nil { rw.WriteHeader(http.StatusInternalServerError) log.Printf("Server error during remove: %v", err) fmt.Fprintf(rw, "Server error") return } httputil.ReturnJSON(rw, &RemoveResponse{Removed: toRemove}) }
func (sh *SyncHandler) runSync(srcName string, enumSrc blobserver.Storage, longPollWait time.Duration) int { if longPollWait != 0 { sh.setStatus("Idle; waiting for new blobs") // TODO: use longPollWait somehow. } enumch := make(chan blob.SizedRef) errch := make(chan error, 1) go func() { errch <- enumSrc.EnumerateBlobs(enumch, "", 1000) }() nCopied := 0 toCopy := 0 workch := make(chan blob.SizedRef, 1000) resch := make(chan copyResult, 8) for sb := range enumch { toCopy++ workch <- sb if toCopy <= sh.copierPoolSize { go sh.copyWorker(resch, workch) } sh.setStatus("Enumerating queued blobs: %d", toCopy) } close(workch) for i := 0; i < toCopy; i++ { sh.setStatus("Copied %d/%d of batch of queued blobs", nCopied, toCopy) res := <-resch // TODO(mpl): why is nCopied incremented while res.err hasn't been checked // yet? Maybe it should be renamed to nTried? nCopied++ sh.lk.Lock() if res.err == nil { sh.totalCopies++ sh.totalCopyBytes += res.sb.Size sh.recentCopyTime = time.Now().UTC() } else { sh.totalErrors++ } sh.lk.Unlock() } if err := <-errch; err != nil { sh.addErrorToLog(fmt.Errorf("replication error for source %q, enumerate from source: %v", srcName, err)) return nCopied } return nCopied }
// src: non-nil source // dest: non-nil destination // thirdLeg: optional third-leg client. if not nil, anything on src // but not on dest will instead be copied to thirdLeg, instead of // directly to dest. (sneakernet mode, copying to a portable drive // and transporting thirdLeg to dest) func (c *syncCmd) doPass(src, dest, thirdLeg blobserver.Storage) (stats SyncStats, retErr error) { srcBlobs := make(chan blob.SizedRef, 100) destBlobs := make(chan blob.SizedRef, 100) srcErr := make(chan error, 1) destErr := make(chan error, 1) go func() { srcErr <- enumerateAllBlobs(src, srcBlobs) }() checkSourceError := func() { if err := <-srcErr; err != nil { retErr = fmt.Errorf("Enumerate error from source: %v", err) } } if c.dest == "stdout" { for sb := range srcBlobs { fmt.Printf("%s %d\n", sb.Ref, sb.Size) } checkSourceError() return } go func() { destErr <- enumerateAllBlobs(dest, destBlobs) }() checkDestError := func() { if err := <-destErr; err != nil { retErr = errors.New(fmt.Sprintf("Enumerate error from destination: %v", err)) } } destNotHaveBlobs := make(chan blob.SizedRef) sizeMismatch := make(chan blob.Ref) readSrcBlobs := srcBlobs if c.verbose { readSrcBlobs = loggingBlobRefChannel(srcBlobs) } mismatches := []blob.Ref{} go client.ListMissingDestinationBlobs(destNotHaveBlobs, sizeMismatch, readSrcBlobs, destBlobs) // Handle three-legged mode if tc is provided. checkThirdError := func() {} // default nop syncBlobs := destNotHaveBlobs firstHopDest := dest if thirdLeg != nil { thirdBlobs := make(chan blob.SizedRef, 100) thirdErr := make(chan error, 1) go func() { thirdErr <- enumerateAllBlobs(thirdLeg, thirdBlobs) }() checkThirdError = func() { if err := <-thirdErr; err != nil { retErr = fmt.Errorf("Enumerate error from third leg: %v", err) } } thirdNeedBlobs := make(chan blob.SizedRef) go client.ListMissingDestinationBlobs(thirdNeedBlobs, sizeMismatch, destNotHaveBlobs, thirdBlobs) syncBlobs = thirdNeedBlobs firstHopDest = thirdLeg } For: for { select { case br := <-sizeMismatch: // TODO(bradfitz): check both sides and repair, carefully. For now, fail. log.Printf("WARNING: blobref %v has differing sizes on source and dest", br) stats.ErrorCount++ mismatches = append(mismatches, br) case sb, ok := <-syncBlobs: if !ok { break For } fmt.Printf("Destination needs blob: %s\n", sb) blobReader, size, err := src.FetchStreaming(sb.Ref) if err != nil { stats.ErrorCount++ log.Printf("Error fetching %s: %v", sb.Ref, err) continue } if size != sb.Size { stats.ErrorCount++ log.Printf("Source blobserver's enumerate size of %d for blob %s doesn't match its Get size of %d", sb.Size, sb.Ref, size) continue } if _, err := firstHopDest.ReceiveBlob(sb.Ref, blobReader); err != nil { stats.ErrorCount++ log.Printf("Upload of %s to destination blobserver failed: %v", sb.Ref, err) continue } stats.BlobsCopied++ stats.BytesCopied += size if c.removeSrc { if err = src.RemoveBlobs([]blob.Ref{sb.Ref}); err != nil { stats.ErrorCount++ log.Printf("Failed to delete %s from source: %v", sb.Ref, err) } } } } checkSourceError() checkDestError() checkThirdError() if retErr == nil && stats.ErrorCount > 0 { retErr = fmt.Errorf("%d errors during sync", stats.ErrorCount) } return stats, retErr }
// src: non-nil source // dest: non-nil destination // thirdLeg: optional third-leg client. if not nil, anything on src // but not on dest will instead be copied to thirdLeg, instead of // directly to dest. (sneakernet mode, copying to a portable drive // and transporting thirdLeg to dest) func (c *syncCmd) doPass(src, dest, thirdLeg blobserver.Storage) (stats SyncStats, retErr error) { var statsMu sync.Mutex // guards stats return value srcBlobs := make(chan blob.SizedRef, 100) destBlobs := make(chan blob.SizedRef, 100) srcErr := make(chan error, 1) destErr := make(chan error, 1) ctx := context.TODO() enumCtx, cancel := context.WithCancel(ctx) // used for all (2 or 3) enumerates defer cancel() enumerate := func(errc chan<- error, sto blobserver.Storage, blobc chan<- blob.SizedRef) { err := enumerateAllBlobs(enumCtx, sto, blobc) if err != nil { cancel() } errc <- err } go enumerate(srcErr, src, srcBlobs) checkSourceError := func() { if err := <-srcErr; err != nil && err != context.Canceled { retErr = fmt.Errorf("Enumerate error from source: %v", err) } } if c.dest == "stdout" { for sb := range srcBlobs { fmt.Fprintf(cmdmain.Stdout, "%s %d\n", sb.Ref, sb.Size) } checkSourceError() return } if c.wipe { // TODO(mpl): dest is a client. make it send a "wipe" request? // upon reception its server then wipes itself if it is a wiper. log.Print("Index wiping not yet supported.") } go enumerate(destErr, dest, destBlobs) checkDestError := func() { if err := <-destErr; err != nil && err != context.Canceled { retErr = fmt.Errorf("Enumerate error from destination: %v", err) } } destNotHaveBlobs := make(chan blob.SizedRef) readSrcBlobs := srcBlobs if c.verbose { readSrcBlobs = loggingBlobRefChannel(srcBlobs) } mismatches := []blob.Ref{} logErrorf := func(format string, args ...interface{}) { log.Printf(format, args...) statsMu.Lock() stats.ErrorCount++ statsMu.Unlock() } onMismatch := func(br blob.Ref) { // TODO(bradfitz): check both sides and repair, carefully. For now, fail. logErrorf("WARNING: blobref %v has differing sizes on source and dest", br) mismatches = append(mismatches, br) } go blobserver.ListMissingDestinationBlobs(destNotHaveBlobs, onMismatch, readSrcBlobs, destBlobs) // Handle three-legged mode if tc is provided. checkThirdError := func() {} // default nop syncBlobs := destNotHaveBlobs firstHopDest := dest if thirdLeg != nil { thirdBlobs := make(chan blob.SizedRef, 100) thirdErr := make(chan error, 1) go enumerate(thirdErr, thirdLeg, thirdBlobs) checkThirdError = func() { if err := <-thirdErr; err != nil && err != context.Canceled { retErr = fmt.Errorf("Enumerate error from third leg: %v", err) } } thirdNeedBlobs := make(chan blob.SizedRef) go blobserver.ListMissingDestinationBlobs(thirdNeedBlobs, onMismatch, destNotHaveBlobs, thirdBlobs) syncBlobs = thirdNeedBlobs firstHopDest = thirdLeg } var gate = syncutil.NewGate(c.concurrency) var wg sync.WaitGroup for sb := range syncBlobs { sb := sb gate.Start() wg.Add(1) go func() { defer wg.Done() defer gate.Done() fmt.Fprintf(cmdmain.Stdout, "Destination needs blob: %s\n", sb) blobReader, size, err := src.Fetch(sb.Ref) if err != nil { logErrorf("Error fetching %s: %v", sb.Ref, err) return } if size != sb.Size { logErrorf("Source blobserver's enumerate size of %d for blob %s doesn't match its Get size of %d", sb.Size, sb.Ref, size) return } _, err = blobserver.Receive(firstHopDest, sb.Ref, blobReader) if err != nil { logErrorf("Upload of %s to destination blobserver failed: %v", sb.Ref, err) return } statsMu.Lock() stats.BlobsCopied++ stats.BytesCopied += int64(size) statsMu.Unlock() if c.removeSrc { if err := src.RemoveBlobs([]blob.Ref{sb.Ref}); err != nil { logErrorf("Failed to delete %s from source: %v", sb.Ref, err) } } }() } wg.Wait() checkSourceError() checkDestError() checkThirdError() if retErr == nil && stats.ErrorCount > 0 { retErr = fmt.Errorf("%d errors during sync", stats.ErrorCount) } return stats, retErr }
// src: non-nil source // dest: non-nil destination // thirdLeg: optional third-leg client. if not nil, anything on src // but not on dest will instead be copied to thirdLeg, instead of // directly to dest. (sneakernet mode, copying to a portable drive // and transporting thirdLeg to dest) func (c *syncCmd) doPass(src, dest, thirdLeg blobserver.Storage) (stats SyncStats, retErr error) { srcBlobs := make(chan blob.SizedRef, 100) destBlobs := make(chan blob.SizedRef, 100) srcErr := make(chan error, 1) destErr := make(chan error, 1) ctx := context.TODO() defer ctx.Cancel() go func() { srcErr <- enumerateAllBlobs(ctx, src, srcBlobs) }() checkSourceError := func() { if err := <-srcErr; err != nil { retErr = fmt.Errorf("Enumerate error from source: %v", err) } } if c.dest == "stdout" { for sb := range srcBlobs { fmt.Fprintf(cmdmain.Stdout, "%s %d\n", sb.Ref, sb.Size) } checkSourceError() return } if c.wipe { // TODO(mpl): dest is a client. make it send a "wipe" request? // upon reception its server then wipes itself if it is a wiper. log.Print("Index wiping not yet supported.") } go func() { destErr <- enumerateAllBlobs(ctx, dest, destBlobs) }() checkDestError := func() { if err := <-destErr; err != nil { retErr = fmt.Errorf("Enumerate error from destination: %v", err) } } destNotHaveBlobs := make(chan blob.SizedRef) readSrcBlobs := srcBlobs if c.verbose { readSrcBlobs = loggingBlobRefChannel(srcBlobs) } mismatches := []blob.Ref{} onMismatch := func(br blob.Ref) { // TODO(bradfitz): check both sides and repair, carefully. For now, fail. log.Printf("WARNING: blobref %v has differing sizes on source and dest", br) stats.ErrorCount++ mismatches = append(mismatches, br) } go blobserver.ListMissingDestinationBlobs(destNotHaveBlobs, onMismatch, readSrcBlobs, destBlobs) // Handle three-legged mode if tc is provided. checkThirdError := func() {} // default nop syncBlobs := destNotHaveBlobs firstHopDest := dest if thirdLeg != nil { thirdBlobs := make(chan blob.SizedRef, 100) thirdErr := make(chan error, 1) go func() { thirdErr <- enumerateAllBlobs(ctx, thirdLeg, thirdBlobs) }() checkThirdError = func() { if err := <-thirdErr; err != nil { retErr = fmt.Errorf("Enumerate error from third leg: %v", err) } } thirdNeedBlobs := make(chan blob.SizedRef) go blobserver.ListMissingDestinationBlobs(thirdNeedBlobs, onMismatch, destNotHaveBlobs, thirdBlobs) syncBlobs = thirdNeedBlobs firstHopDest = thirdLeg } for sb := range syncBlobs { fmt.Fprintf(cmdmain.Stdout, "Destination needs blob: %s\n", sb) blobReader, size, err := src.FetchStreaming(sb.Ref) if err != nil { stats.ErrorCount++ log.Printf("Error fetching %s: %v", sb.Ref, err) continue } if size != sb.Size { stats.ErrorCount++ log.Printf("Source blobserver's enumerate size of %d for blob %s doesn't match its Get size of %d", sb.Size, sb.Ref, size) continue } if _, err := blobserver.Receive(firstHopDest, sb.Ref, blobReader); err != nil { stats.ErrorCount++ log.Printf("Upload of %s to destination blobserver failed: %v", sb.Ref, err) continue } stats.BlobsCopied++ stats.BytesCopied += int64(size) if c.removeSrc { if err = src.RemoveBlobs([]blob.Ref{sb.Ref}); err != nil { stats.ErrorCount++ log.Printf("Failed to delete %s from source: %v", sb.Ref, err) } } } checkSourceError() checkDestError() checkThirdError() if retErr == nil && stats.ErrorCount > 0 { retErr = fmt.Errorf("%d errors during sync", stats.ErrorCount) } return stats, retErr }
func CheckEnumerate(sto blobserver.Storage, wantUnsorted []blob.SizedRef, opts ...interface{}) error { var after string var n = 1000 for _, opt := range opts { switch v := opt.(type) { case string: after = v case int: n = v default: panic("bad option of type " + fmt.Sprintf("%T", v)) } } want := append([]blob.SizedRef(nil), wantUnsorted...) sort.Sort(blob.SizedByRef(want)) sbc := make(chan blob.SizedRef, 10) var got []blob.SizedRef var grp syncutil.Group sawEnd := make(chan bool, 1) grp.Go(func() error { ctx := context.New() defer ctx.Cancel() if err := sto.EnumerateBlobs(ctx, sbc, after, n); err != nil { return fmt.Errorf("EnumerateBlobs(%q, %d): %v", after, n, err) } return nil }) grp.Go(func() error { var lastRef blob.Ref for sb := range sbc { if !sb.Valid() { return fmt.Errorf("invalid blobref %#v received in enumerate", sb) } got = append(got, sb) if lastRef.Valid() && sb.Ref.Less(lastRef) { return fmt.Errorf("blobs appearing out of order") } lastRef = sb.Ref } sawEnd <- true return nil }) grp.Go(func() error { select { case <-sawEnd: return nil case <-time.After(10 * time.Second): return errors.New("timeout waiting for EnumerateBlobs to close its channel") } }) if err := grp.Err(); err != nil { return fmt.Errorf("Enumerate error: %v", err) } if len(got) == 0 && len(want) == 0 { return nil } var gotSet = map[blob.SizedRef]bool{} for _, sb := range got { if gotSet[sb] { return fmt.Errorf("duplicate blob %v returned in enumerate", sb) } gotSet[sb] = true } if !reflect.DeepEqual(got, want) { return fmt.Errorf("Enumerate mismatch. Got %d; want %d.\n Got: %v\nWant: %v\n", len(got), len(want), got, want) } return nil }