func server(addr TCPEndPoint, serveOnce /*test flag*/ bool, timeout int) { serverConnectionTimeout := time.Duration(timeout) * time.Second // listen on all interfaces EndPoint := addr.Host + ":" + strconv.Itoa(int(addr.Port)) laddr, err := net.ResolveTCPAddr("tcp", EndPoint) if err != nil { log.Fatal("Connection listener address resolution error:", err) } ln, err := net.ListenTCP("tcp", laddr) if err != nil { log.Fatal("Connection listener error:", err) } defer ln.Close() ln.SetDeadline(time.Now().Add(serverConnectionTimeout)) log.Info("Sync server is up...") for { conn, err := ln.AcceptTCP() if err != nil { log.Fatal("Connection accept error:", err) } if serveOnce { // This is to avoid server listening port conflicts while running tests // exit after single connection request if serveConnection(conn) { break // no retries } log.Warn("Server: waiting for client sync retry...") } else { go serveConnection(conn) } } log.Info("Sync server exit.") }
// FailPointFileHashMatch returns true if this failpoint is set, clears the failpoint func FailPointFileHashMatch() bool { mutex.Lock() val := failFileHashMatch if val { log.Warn("FailPointFileHashMatch!") failFileHashMatch = false } mutex.Unlock() return val }
// SyncFile synchronizes local file to remote host func SyncFile(localPath string, addr TCPEndPoint, remotePath string, timeout int) (hashLocal []byte, err error) { for retries := 1; retries >= 0; retries-- { hashLocal, err = syncFile(localPath, addr, remotePath, timeout, retries > 0) if err != nil { if _, ok := err.(*HashCollsisionError); ok { // retry on HahsCollisionError log.Warn("SSync: retrying on chunk hash collision...") continue } else { log.Error("SSync error:", err) } } break } return }
func connect(host, port string, timeout int) net.Conn { // connect to this socket endpoint := host + ":" + port raddr, err := net.ResolveTCPAddr("tcp", endpoint) if err != nil { log.Fatal("Connection address resolution error:", err) } timeStart := time.Now() timeStop := timeStart.Add(time.Duration(timeout) * time.Second) for timeNow := timeStart; timeNow.Before(timeStop); timeNow = time.Now() { conn, err := net.DialTCP("tcp", nil, raddr) if err == nil { return conn } log.Warn("Failed connection to", endpoint, "Retrying...") if timeNow != timeStart { // only sleep after the second attempt to speedup tests time.Sleep(1 * time.Second) } } return nil }
func processDiff(salt []byte, abortStream chan<- error, errStream <-chan error, encoder *gob.Encoder, decoder *gob.Decoder, local <-chan HashedDataInterval, remote <-chan HashedInterval, netInStreamDone <-chan bool, retry bool) (hashLocal []byte, err error) { // Local: __ _* // Remote: *_ ** hashLocal = make([]byte, 0) // empty hash for errors const concurrentReaders = 4 netStream := make(chan diffChunk, 128) netStatus := make(chan netXferStatus) go networkSender(netStream, encoder, netStatus) fileHasher := sha1.New() fileHasher.Write(salt) lrange := <-local rrange := <-remote for lrange.Len() != 0 { if rrange.Len() == 0 { // Copy local tail if verboseClient { logData("LHASH", lrange.Data) } hashFileData(fileHasher, lrange.Len(), lrange.Data) processFileInterval(lrange, HashedInterval{FileInterval{SparseHole, lrange.Interval}, make([]byte, 0)}, netStream) lrange = <-local continue } // Diff if verboseClient { log.Debug("Diff:", lrange.HashedInterval, rrange) } if lrange.Begin == rrange.Begin { if lrange.End > rrange.End { data := lrange.Data if len(data) > 0 { data = lrange.Data[:rrange.Len()] } subrange := HashedDataInterval{HashedInterval{FileInterval{lrange.Kind, rrange.Interval}, lrange.Hash}, data} if verboseClient { logData("LHASH", subrange.Data) } hashFileData(fileHasher, subrange.Len(), subrange.Data) processFileInterval(subrange, rrange, netStream) if len(data) > 0 { lrange.Data = lrange.Data[subrange.Len():] } lrange.Begin = rrange.End rrange = <-remote continue } else if lrange.End < rrange.End { if verboseClient { logData("LHASH", lrange.Data) } hashFileData(fileHasher, lrange.Len(), lrange.Data) processFileInterval(lrange, HashedInterval{FileInterval{rrange.Kind, lrange.Interval}, make([]byte, 0)}, netStream) rrange.Begin = lrange.End lrange = <-local continue } if verboseClient { logData("LHASH", lrange.Data) } hashFileData(fileHasher, lrange.Len(), lrange.Data) processFileInterval(lrange, rrange, netStream) lrange = <-local rrange = <-remote } else { // Should never happen log.Fatal("processDiff internal error") return } } log.Info("Finished processing file diff") status := true err = <-errStream if err != nil { log.Error("Sync client file load aborted:", err) status = false } // make sure we finished consuming dst hashes status = <-netInStreamDone && status // netDstReceiver finished log.Info("Finished consuming remote file hashes, status=", status) // Send end of transmission netStream <- diffChunk{true, DataInterval{FileInterval{SparseIgnore, Interval{0, 0}}, make([]byte, 0)}} // get network sender status net := <-netStatus log.Info("Finished sending file diff of", net.byteCount, "(bytes), status=", net.status) if !net.status { err = errors.New("netwoek transfer failure") return } var statusRemote bool err = decoder.Decode(&statusRemote) if err != nil { log.Fatal("Cient protocol remote status error:", err) return } if !statusRemote { err = errors.New("failure on remote sync site") return } var hashRemote []byte err = decoder.Decode(&hashRemote) if err != nil { log.Fatal("Cient protocol remote hash error:", err) return } // Compare file hashes hashLocal = fileHasher.Sum(nil) if isHashDifferent(hashLocal, hashRemote) || FailPointFileHashMatch() { log.Warn("hashLocal =", hashLocal) log.Warn("hashRemote=", hashRemote) err = &HashCollsisionError{} } else { retry = false // success, don't retry anymore } // Final retry negotiation { err1 := encoder.Encode(retry) if err1 != nil { log.Fatal("Cient protocol remote retry error:", err) } err1 = decoder.Decode(&statusRemote) if err1 != nil { log.Fatal("Cient protocol remote retry status error:", err) } } return }