Example #1
0
// find and record the next hop route for c.
// Additionally, all hops along this route are also the shortest path, so
// record those as well to save on effort.
func (n *Node) updateRoute(c string) {
	if len(n.effectiveNetwork) == 0 {
		return
	}

	if log.WillLog(log.DEBUG) {
		log.Debug("updating route for %v", c)
	}

	routes := make(map[string]string) // a key node has a value of the previous hop, the key exists if it's been visited
	routes[n.name] = n.name           // the route to ourself is pretty easy to calculate

	// dijkstra's algorithm is well suited in go - we can use a buffered
	// channel of nodes to order our search. We start by putting ourselves
	// in the queue (channel) and follow all nodes connected to it. If we
	// haven't visited that node before, it goes in the queue.
	q := make(chan string, len(n.effectiveNetwork))
	q <- n.name

	for len(q) != 0 {
		v := <-q

		if log.WillLog(log.DEBUG) {
			log.Debug("visiting %v", v)
		}

		for _, a := range n.effectiveNetwork[v] {
			if _, ok := routes[a]; !ok {
				q <- a
				if log.WillLog(log.DEBUG) {
					log.Debug("previous hop for %v is %v", a, v)
				}
				routes[a] = v // this is the route to node a from v
			}
		}

		if v == c {
			break
		}
	}

	for k, v := range routes {
		curr := v
		prev := k
		r := k
		if curr == n.name {
			r += "<-" + routes[curr]
		}
		for curr != n.name {
			prev = curr
			curr = routes[curr]
			r += "<-" + routes[curr]
		}
		if log.WillLog(log.DEBUG) {
			log.Debug("full route for %v is %v", k, r)
		}
		n.routes[k] = prev
	}
}
Example #2
0
// Send a message according to the parameters set in the message.
// Users will generally use the Set and Broadcast functions instead of Send.
// The returned error is always nil if the message type is broadcast.
// If an error is encountered, Send returns immediately.
func (n *Node) Send(m *Message) ([]string, error) {
	if log.WillLog(log.DEBUG) {
		log.Debug("Send: %v", m)
	}

	// force updating the network if needed on Send()
	n.checkUpdateNetwork()

	routeSlices, err := n.getRoutes(m)
	if err != nil {
		return nil, err
	}

	if log.WillLog(log.DEBUG) {
		log.Debug("routeSlices: %v", routeSlices)
	}

	errChan := make(chan error)
	for k, v := range routeSlices {
		go func(client string, recipients []string) {
			mOne := &Message{
				Recipients:   recipients,
				Source:       m.Source,
				CurrentRoute: m.CurrentRoute,
				Command:      m.Command,
				Body:         m.Body,
			}
			err := n.clientSend(client, mOne)
			if err != nil {
				errChan <- err
			} else {
				errChan <- nil
			}
		}(k, v)
	}

	// wait on all of the client sends to complete
	var ret string
	for i := 0; i < len(routeSlices); i++ {
		r := <-errChan
		if r != nil {
			ret += r.Error() + "\n"
		}
	}

	// Rebuild the recipients from the routeSlices so that the caller can know
	// which recipients were actually valid.
	recipients := []string{}
	for _, r := range routeSlices {
		recipients = append(recipients, r...)
	}

	if ret == "" {
		return recipients, nil
	}

	return recipients, errors.New(ret)
}
Example #3
0
// generateEffectiveNetwork returns the subset of the current known topology that contains
// pairs of known connections. That is, if node A says it's connected to node B
// but node B does not say it's connected to node A, then that connection will
// not be listed in the effective mesh list.
// generateEffectiveNetwork expects to be called with meshLock obtained.
func (n *Node) generateEffectiveNetwork() {
	log.Debugln("generateEffectiveNetwork")

	for {
		emesh := make(mesh)
		for k, v := range n.network {
		effectiveNetworkLoop:
			for _, i := range v { // for each connection i to node k, see if i also reports being connected to k
				for _, j := range emesh[i] { // do we already have this connection noted? if so, move on. This should happen zero or one times for each node.
					if j == k {
						continue effectiveNetworkLoop
					}
				}
				for _, j := range n.network[i] { // go through all of node i's connections looking for k
					if j == k {
						if log.WillLog(log.DEBUG) {
							log.Debug("found pair %v <-> %v", k, i)
						}
						// note the connection in the adjacency list for both i and k
						emesh[k] = append(emesh[k], i)
						emesh[i] = append(emesh[i], k)
						break
					}
				}
			}
		}
		n.effectiveNetwork = emesh

		// now generate routes to each of the nodes from us based on the effectiveNetwork
		n.routes = make(map[string]string)

		// attempt to learn routes to each node from this node,
		// assuming that all nodes in the effective network are
		// routable.  It's possible that the effective network
		// represents partitioned meshes, so if we cannot find a route
		// to a node, remove it from the known network and start this
		// entire process over.
		stable := true
		for h, _ := range n.effectiveNetwork {
			if _, ok := n.routes[h]; !ok {
				n.updateRoute(h)
				if _, ok := n.routes[h]; !ok {
					log.Debug("removing unroutable node %v", h)
					delete(n.network, h)
					stable = false
				}
			}
		}
		if stable {
			break
		}
	}

	if log.WillLog(log.DEBUG) {
		log.Debug("new effectiveNetwork: %v", n.effectiveNetwork)
	}
}
Example #4
0
// Transfer a single filepart to a temporary transfer directory.
func (iom *IOMeshage) Xfer(filename string, part int64, from string) error {
	TID := genTID()
	c := make(chan *IOMMessage)
	err := iom.registerTID(TID, c)
	defer iom.unregisterTID(TID)

	if err != nil {
		// a collision in int64, we should tell someone about this
		log.Fatalln(err)
	}

	m := &IOMMessage{
		From:     iom.node.Name(),
		Type:     TYPE_XFER,
		Filename: filename,
		TID:      TID,
		Part:     part,
	}
	_, err = iom.node.Set([]string{from}, m)
	if err != nil {
		return err
	}

	// wait for a response, or a timeout
	select {
	case resp := <-c:
		if log.WillLog(log.DEBUG) {
			log.Debugln("got part: ", resp.Part)
		}
		if resp.ACK {
			if log.WillLog(log.DEBUG) {
				log.Debugln("got part from: ", resp.From)
			}
			// write the part out to disk
			iom.transferLock.RLock()
			defer iom.transferLock.RUnlock()
			if t, ok := iom.transfers[filename]; ok {
				outfile := fmt.Sprintf("%v/%v.part_%v", t.Dir, filepath.Base(filename), part)
				err := ioutil.WriteFile(outfile, resp.Data, 0664)
				if err != nil {
					return err
				}
			} else {
				return fmt.Errorf("no transfer temporary directory to write to!")
			}
		} else {
			return fmt.Errorf("received NACK from xfer node")
		}
	case <-time.After(timeout):
		return fmt.Errorf("timeout")
	}
	return nil
}
Example #5
0
func (n *Node) handleMSA(m *Message) {
	log.Debug("handleMSA: %v", m)

	n.meshLock.Lock()
	defer n.meshLock.Unlock()

	if len(n.network[m.Source]) == len(m.Body.([]string)) {
		diff := false
		for i, v := range n.network[m.Source] {
			if m.Body.([]string)[i] != v {
				diff = true
				break
			}
		}
		if !diff {
			log.Debugln("MSA discarded, client data hasn't changed")
			return
		}
	}

	n.network[m.Source] = m.Body.([]string)

	if log.WillLog(log.DEBUG) {
		log.Debug("new network is: %v", n.network)
	}

	n.updateNetwork = true
}
Example #6
0
func (n *Node) clientSend(host string, m *Message) error {
	if log.WillLog(log.DEBUG) {
		log.Debug("clientSend %s: %v", host, m)
	}
	if c, ok := n.clients[host]; ok {
		c.lock.Lock()
		defer c.lock.Unlock()

		err := c.enc.Encode(m)
		if err != nil {
			c.conn.Close()
			return err
		}

		// wait for a response
		for {
			select {
			case ID := <-c.ack:
				if ID == m.ID {
					return nil
				}
			case <-time.After(n.timeout):
				c.conn.Close()
				return errors.New("timeout")
			}
		}
	}
	return fmt.Errorf("no such client %s", host)
}
Example #7
0
// Handle incoming "get file info" messages by looking up if we have the file
// and responding with the number of parts or a NACK.  Also process directories
// and globs, populating the Glob field of the IOMMessage if needed.
func (iom *IOMeshage) handleInfo(m *IOMMessage) {
	// do we have this file, rooted at iom.base?
	resp := IOMMessage{
		From:     iom.node.Name(),
		Type:     TYPE_RESPONSE,
		Filename: m.Filename,
		TID:      m.TID,
	}

	glob, parts, err := iom.fileInfo(filepath.Join(iom.base, m.Filename))
	if err != nil {
		resp.ACK = false
	} else if len(glob) == 1 && glob[0] == m.Filename {
		resp.ACK = true
		resp.Part = parts
		fi, err := os.Stat(filepath.Join(iom.base, m.Filename))
		if err != nil {
			resp.ACK = false
		} else {
			resp.Perm = fi.Mode() & os.ModePerm
		}
		if log.WillLog(log.DEBUG) {
			log.Debugln("handleInfo found file with parts: ", resp.Part)
		}
	} else {
		// populate Glob
		resp.ACK = true
		resp.Glob = glob
	}

	_, err = iom.node.Set([]string{m.From}, resp)
	if err != nil {
		log.Errorln("handleInfo: sending message: ", err)
	}
}
Example #8
0
// Get file info and return the number of parts in the file. If the filename is
// a directory or glob, return the list of files the directory/glob contains.
func (iom *IOMeshage) fileInfo(filename string) ([]string, int64, error) {
	glob, err := filepath.Glob(filename)
	if err != nil {
		return nil, 0, err
	}
	if len(glob) > 1 {
		// globs are recursive, figure out any directories
		var globsRet []string
		for _, v := range glob {
			rGlob, _, err := iom.fileInfo(v)
			if err != nil {
				return nil, 0, err
			}
			globsRet = append(globsRet, rGlob...)
		}
		return globsRet, 0, nil
	}

	f, err := os.Open(filename)
	if err != nil {
		return nil, 0, err
	}
	defer f.Close()

	// is this a directory
	fi, err := f.Stat()
	if err != nil {
		if log.WillLog(log.DEBUG) {
			log.Debugln("fileInfo error stat: ", err)
		}
		return nil, 0, err
	}
	if fi.IsDir() {
		// walk the directory and populate glob
		glob = []string{}
		err := filepath.Walk(filename, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			rel, err := filepath.Rel(iom.base, path)
			if err != nil {
				return err
			}
			glob = append(glob, rel)
			return nil
		})
		if err != nil {
			return nil, 0, err
		}
		return glob, 0, nil
	}

	// we do have the file, calculate the number of parts
	parts := (fi.Size() + PART_SIZE - 1) / PART_SIZE // integer divide with ceiling instead of floor
	rel, err := filepath.Rel(iom.base, filename)
	return []string{rel}, parts, nil
}
Example #9
0
func (n *Node) flood(m *Message) {
	if log.WillLog(log.DEBUG) {
		log.Debug("flood: %v", m)
	}

	n.clientLock.Lock()
	defer n.clientLock.Unlock()

floodLoop:
	for k, _ := range n.clients {
		for _, j := range m.CurrentRoute {
			if k == j {
				continue floodLoop
			}
		}
		go func(j string, m *Message) {
			err := n.clientSend(j, m)
			if err != nil {
				// is j still a client?
				if n.hasClient(j) {
					log.Error("flood to client %v: %v", j, err)
				}
			}
		}(k, m)
	}
}
Example #10
0
// MSA issues a Meshage State Annoucement, which contains a list of all the nodes connected to the broadcaster
func (n *Node) MSA() {
	log.Debugln("MSA")

	// rate limit MSA spam to once per MSA timeout / 2
	n.msaLock.Lock()
	defer n.msaLock.Unlock()
	if time.Now().Sub(n.lastMSA) < (n.msaTimeout / 2) {
		return
	}
	n.lastMSA = time.Now()

	n.clientLock.Lock()
	var clients []string
	for k, _ := range n.clients {
		clients = append(clients, k)
	}
	n.clientLock.Unlock()

	sort.Strings(clients)

	n.meshLock.Lock()
	diff := false
	if len(n.network[n.name]) != len(clients) {
		diff = true
	} else {
		for i, v := range n.network[n.name] {
			if clients[i] != v {
				diff = true
				break
			}
		}
	}
	if diff {
		log.Debugln("client list changed, recalculating topology")
		n.network[n.name] = clients
		n.updateNetwork = true
	}
	n.meshLock.Unlock()

	if log.WillLog(log.DEBUG) {
		log.Debug("client list: %v", clients)
	}

	if len(clients) == 0 {
		log.Debugln("not issuing MSA, no connected clients")
		return
	}

	m := &Message{
		Source:       n.name,
		CurrentRoute: []string{n.name},
		ID:           n.sequence(),
		Command:      MSA,
		Body:         clients,
	}
	n.flood(m)
}
Example #11
0
// clientHandler is called as a goroutine after a successful handshake. It
// begins by issuing an MSA. When the receiver exits, another MSA is issued
// without the client.
func (n *Node) clientHandler(host string) {
	log.Debug("clientHandler: %v", host)

	c, err := n.getClient(host)
	if err != nil {
		log.Error("client %v vanished -- %v", host, err)
		return
	}

	n.MSA()

	for {
		var m Message
		c.conn.SetReadDeadline(time.Now().Add(deadlineMultiplier * n.msaTimeout))
		err := c.dec.Decode(&m)
		if err != nil {
			if err != io.EOF && !strings.Contains(err.Error(), "connection reset by peer") {
				log.Error("client %v decode: %v", host, err)
			}
			break
		}
		if log.WillLog(log.DEBUG) {
			log.Debug("decoded message: %v: %v", c.name, &m)
		}
		if m.Command == ACK {
			c.ack <- m.ID
		} else {
			// send an ack
			a := Message{
				Command: ACK,
				ID:      m.ID,
			}
			c.conn.SetWriteDeadline(time.Now().Add(deadlineMultiplier * n.msaTimeout))
			err := c.enc.Encode(a)
			if err != nil {
				if err != io.EOF {
					log.Error("client %v encode ACK: %v", host, err)
				}
				break
			}
			n.messagePump <- &m
		}
	}
	log.Info("client %v disconnected", host)

	// client has disconnected
	c.conn.Close()
	n.clientLock.Lock()
	delete(n.clients, c.name)
	n.clientLock.Unlock()
	go n.checkDegree()

	n.MSA()
}
Example #12
0
// Handle incoming responses (ACK, file transfer, etc.). It's possible for an
// incoming response to be invalid, such as when a message times out and the
// receiver is no longer expecting the message to arrive. If so, drop the
// message. Responses are sent along registered channels, which are closed when
// the receiver gives up. If we try to send on a closed channel, recover and
// move on.
func (iom *IOMeshage) handleResponse(m *IOMMessage) {
	if c, ok := iom.TIDs[m.TID]; ok {
		defer func() {
			recover()
			if log.WillLog(log.DEBUG) {
				log.Debugln("send on closed channel recovered")
			}
		}()
		c <- m
	} else {
		log.Errorln("dropping message for invalid TID: ", m.TID)
	}
}
Example #13
0
// messageHandler accepts messages from all connected clients and forwards them to the
// appropriate handlers, and to the receiver channel should the message be intended for this
// node.
func (n *Node) messageHandler() {
	log.Debugln("messageHandler")
	for {
		m := <-n.messagePump
		if log.WillLog(log.DEBUG) {
			log.Debug("messageHandler: %v", m)
		}
		m.CurrentRoute = append(m.CurrentRoute, n.name)

		switch m.Command {
		case MSA:
			n.sequenceLock.Lock()
			if m.ID == 1 && n.sequences[m.Source] > LOLLIPOP_LENGTH {
				n.sequences[m.Source] = 0
			}
			if m.ID > n.sequences[m.Source] {
				n.sequences[m.Source] = m.ID

				go n.handleMSA(m)
				go n.flood(m)
			} else {
				log.Debug("dropping broadcast: %v:%v", m.Source, m.ID)
			}
			n.sequenceLock.Unlock()
		case MESSAGE:
			var newRecipients []string
			runLocal := false
			for _, i := range m.Recipients {
				if i == n.name {
					runLocal = true
				} else {
					newRecipients = append(newRecipients, i)
				}
			}
			m.Recipients = newRecipients

			go n.Send(m)
			if runLocal {
				go n.handleMessage(m)
			} else {
				if n.Snoop != nil {
					go n.Snoop(m)
				}
			}
		default:
			log.Errorln("invalid message command: ", m.Command)
		}
	}
}
Example #14
0
// Handle incoming responses (ACK, file transfer, etc.). It's possible for an
// incoming response to be invalid, such as when a message times out and the
// receiver is no longer expecting the message to arrive. If so, drop the
// message. Responses are sent along registered channels, which are closed when
// the receiver gives up. If we try to send on a closed channel, recover and
// move on.
func (iom *IOMeshage) handleResponse(m *IOMMessage) {
	iom.tidLock.Lock()
	c, ok := iom.TIDs[m.TID]
	iom.tidLock.Unlock()

	if !ok {
		log.Errorln("dropping message for invalid TID: ", m.TID)
		return
	}

	defer func() {
		recover()
		if log.WillLog(log.DEBUG) {
			log.Debugln("send on closed channel recovered")
		}
	}()

	c <- m
}
Example #15
0
// Message pump for incoming iomeshage messages.
func (iom *IOMeshage) handleMessages() {
	for {
		message := (<-iom.Messages).Body.(IOMMessage)
		m := &message
		if log.WillLog(log.DEBUG) {
			log.Debug("got iomessage from %v, type %v", m.From, m.Type)
		}
		switch m.Type {
		case TYPE_INFO:
			go iom.handleInfo(m)
		case TYPE_WHOHAS:
			go iom.handleWhohas(m)
		case TYPE_XFER:
			go iom.handleXfer(m)
		case TYPE_RESPONSE:
			go iom.handleResponse(m)
		default:
			log.Errorln("iomeshage: received invalid message type: ", m.Type)
		}
	}
}
Example #16
0
// search the mesh for the file/glob/directory, returning a slice of string
// matches. The search includes local matches.
func (iom *IOMeshage) Info(file string) []string {
	var ret []string

	// search locally
	files, _, _ := iom.fileInfo(filepath.Join(iom.base, file))
	ret = append(ret, files...)

	// search the mesh
	TID := genTID()
	c := make(chan *IOMMessage)
	err := iom.registerTID(TID, c)
	defer iom.unregisterTID(TID)

	if err != nil {
		// a collision in int64, we should tell someone about this
		log.Fatalln(err)
	}

	m := &IOMMessage{
		From:     iom.node.Name(),
		Type:     TYPE_INFO,
		Filename: file,
		TID:      TID,
	}
	recipients, err := iom.node.Broadcast(m)
	if err != nil {
		log.Errorln(err)
		return nil
	}
	if log.WillLog(log.DEBUG) {
		log.Debug("sent info request to %v nodes", len(recipients))
	}

	// wait for n responses, or a timeout
	for i := 0; i < len(recipients); i++ {
		select {
		case resp := <-c:
			if log.WillLog(log.DEBUG) {
				log.Debugln("got response: ", resp)
			}
			if resp.ACK {
				if log.WillLog(log.DEBUG) {
					log.Debugln("got info from: ", resp.From)
				}
				if len(resp.Glob) == 0 {
					// exact match unless the exact match is the original glob
					if !strings.Contains(resp.Filename, "*") {
						ret = append(ret, resp.Filename)
					}
				} else {
					ret = append(ret, resp.Glob...)
				}
			}
		case <-time.After(timeout):
			log.Errorln(fmt.Errorf("timeout"))
			return nil
		}
	}

	return ret
}
Example #17
0
// Respond to message m with an ACK if a filepart exists, and optionally the
// contents of that filepart.
func (iom *IOMeshage) handlePart(m *IOMMessage, xfer bool) {
	// do we have this file, rooted at iom.base?
	resp := IOMMessage{
		From:     iom.node.Name(),
		Type:     TYPE_RESPONSE,
		Filename: m.Filename,
		TID:      m.TID,
	}

	iom.drainLock.RLock()
	defer iom.drainLock.RUnlock()

	_, _, err := iom.fileInfo(filepath.Join(iom.base, m.Filename))
	if err != nil {
		resp.ACK = false
	} else {
		resp.ACK = true
		resp.Part = m.Part
		if xfer {
			resp.Data = iom.readPart(m.Filename, m.Part)
		}
		if log.WillLog(log.DEBUG) {
			log.Debugln("handlePart found file with parts: ", resp.Part)
		}
	}

	if resp.ACK {
		_, err = iom.node.Set([]string{m.From}, resp)
		if err != nil {
			log.Errorln("handlePart: sending message: ", err)
		}
		return
	}

	// we don't have the file in a complete state at least, do we have that specific part in flight somewhere?
	// we consider a part to be transferrable IFF it exists on disk and is marked as being fully received.
	iom.transferLock.RLock()
	if t, ok := iom.transfers[m.Filename]; ok {
		// we are currently transferring parts of the file
		if t.Parts[m.Part] {
			partname := fmt.Sprintf("%v/%v.part_%v", t.Dir, t.Filename, m.Part)
			_, _, err := iom.fileInfo(partname)
			if err == nil {
				// we have it
				resp.ACK = true
				resp.Part = m.Part
				if xfer {
					resp.Data = iom.readPart(partname, 0)
					log.Debug("sending partial %v", partname)
				}
			} else {
				resp.ACK = false
			}
		}
	}
	iom.transferLock.RUnlock()

	_, err = iom.node.Set([]string{m.From}, resp)
	if err != nil {
		log.Errorln("handlePart: sending message: ", err)
	}
}
Example #18
0
// Get a file with numParts parts. getParts will randomize the order of the
// parts to maximize the distributed transfer behavior of iomeshage when used
// at scale.
func (iom *IOMeshage) getParts(filename string, numParts int64, perm os.FileMode) {
	// corner case - empty file
	if numParts == 0 {
		log.Debug("file %v has 0 parts, creating empty file", filename)

		// create subdirectories
		fullPath := filepath.Join(iom.base, filename)
		err := os.MkdirAll(filepath.Dir(fullPath), 0755)
		if err != nil {
			log.Errorln(err)
			return
		}
		f, err := os.Create(fullPath)
		if err != nil {
			log.Errorln(err)
			return
		}
		f.Close()
		log.Debug("changing permissions: %v %v", fullPath, perm)
		err = os.Chmod(fullPath, perm)
		if err != nil {
			log.Errorln(err)
		}
		return
	}

	// create a random list of parts to grab
	var parts []int64
	var i int64
	for i = 0; i < numParts; i++ {
		parts = append(parts, i)
	}

	// fisher-yates shuffle
	s := rand.NewSource(time.Now().UnixNano())
	r := rand.New(s)
	for i = numParts - 1; i > 0; i-- {
		j := r.Int63n(i + 1)
		t := parts[j]
		parts[j] = parts[i]
		parts[i] = t
	}

	// create a transfer object
	tdir, err := ioutil.TempDir(iom.base, "transfer_")
	if err != nil {
		log.Errorln(err)
		return
	}
	iom.transferLock.Lock()
	iom.transfers[filename] = &Transfer{
		Dir:      tdir,
		Filename: filename,
		Parts:    make(map[int64]bool),
		NumParts: len(parts),
		Inflight: -1,
		Queued:   true,
	}
	iom.transferLock.Unlock()
	defer iom.destroyTempTransfer(filename)

	// get in line
	iom.queue <- true
	defer func() {
		<-iom.queue
	}()

	iom.transferLock.Lock()
	iom.transfers[filename].Queued = false
	iom.transferLock.Unlock()

	for _, p := range parts {
		// did I already get this part via another node's request?
		iom.transferLock.Lock()
		if iom.transfers[filename].Parts[p] {
			iom.transferLock.Unlock()
			continue
		}
		iom.transfers[filename].Inflight = p
		iom.transferLock.Unlock()

		// attempt to get this part up to MAX_ATTEMPTS attempts
		for attempt := 0; attempt < MAX_ATTEMPTS; attempt++ {
			TID := genTID()
			c := make(chan *IOMMessage)
			err := iom.registerTID(TID, c)

			if err != nil {
				// a collision in int64, we should tell someone about this
				log.Fatalln(err)
			}

			if log.WillLog(log.DEBUG) {
				log.Debug("transferring filepart %v:%v, attempt %v", filename, p, attempt)
			}
			if attempt > 0 {
				// we're most likely issuing multiple attempts because of heavy traffic, wait a bit for things to calm down
				time.Sleep(timeout)
			}
			m := &IOMMessage{
				From:     iom.node.Name(),
				Type:     TYPE_WHOHAS,
				Filename: filename,
				TID:      TID,
				Part:     p,
			}

			recipients, err := iom.node.Broadcast(m)
			if err != nil {
				log.Errorln(err)
				iom.unregisterTID(TID)
				continue
			}
			if log.WillLog(log.DEBUG) {
				log.Debug("sent info request to %v nodes", len(recipients))
			}

			var info *IOMMessage
			var gotPart bool
			var timeoutCount int
			// wait for n responses, or a timeout
		IOMESHAGE_WHOHAS_LOOP:
			for i := 0; i < len(recipients); i++ {
				select {
				case resp := <-c:
					if log.WillLog(log.DEBUG) {
						log.Debugln("got response: ", resp)
					}
					if resp.ACK {
						if log.WillLog(log.DEBUG) {
							log.Debugln("got partInfo from: ", resp.From)
						}
						info = resp
						gotPart = true
						break IOMESHAGE_WHOHAS_LOOP
					}
				case <-time.After(timeout):
					log.Errorln("timeout")
					timeoutCount++

					if timeoutCount == MAX_ATTEMPTS {
						log.Debugln("too many timeouts")
						break IOMESHAGE_WHOHAS_LOOP
					}
					continue
				}
			}
			if !gotPart {
				log.Errorln("part not found")
				iom.unregisterTID(TID)
				continue
			}

			if log.WillLog(log.DEBUG) {
				log.Debug("found part %v on node %v", info.Part, info.From)
			}

			// transfer this part
			err = iom.Xfer(m.Filename, info.Part, info.From)
			if err != nil {
				log.Errorln(err)
				iom.unregisterTID(TID)
				continue
			}
			iom.transferLock.Lock()
			iom.transfers[filename].Parts[p] = true
			iom.transferLock.Unlock()
			iom.unregisterTID(TID)
			break
		}
		iom.transferLock.RLock()
		if !iom.transfers[filename].Parts[p] {
			log.Error("could not transfer filepart %v:%v after %v attempts", filename, p, MAX_ATTEMPTS)
			iom.transferLock.RUnlock()
			return
		}
		iom.transferLock.RUnlock()
	}

	// copy the parts into the whole file
	iom.transferLock.RLock()
	t := iom.transfers[filename]
	iom.transferLock.RUnlock()
	tfile, err := ioutil.TempFile(t.Dir, "cat_")
	if err != nil {
		log.Errorln(err)
	}

	for i = 0; i < numParts; i++ {
		fname := fmt.Sprintf("%v/%v.part_%v", t.Dir, filepath.Base(filename), i)
		fpart, err := os.Open(fname)
		if err != nil {
			log.Errorln(err)
			tfile.Close()
			return
		}
		io.Copy(tfile, fpart)
		fpart.Close()
	}
	name := tfile.Name()
	tfile.Close()

	// create subdirectories
	fullPath := filepath.Join(iom.base, filename)
	err = os.MkdirAll(filepath.Dir(fullPath), 0755)
	if err != nil {
		log.Errorln(err)
		return
	}
	os.Rename(name, fullPath)

	log.Debug("changing permissions: %v %v", fullPath, perm)
	err = os.Chmod(fullPath, perm)
	if err != nil {
		log.Errorln(err)
	}
}
Example #19
0
// Retrieve a file from the shortest path node that has it. Get blocks until
// the file transfer is begins or errors out. If the file specified is a
// directory, the entire directory will be recursively transferred.
// If the file already exists on this node, Get will return immediately with no
// error.
func (iom *IOMeshage) Get(file string) error {
	// is this a directory or a glob
	fi, err := os.Stat(filepath.Join(iom.base, file))
	if err == nil && !fi.IsDir() {
		return nil
	}

	// is this file already in flight?
	iom.transferLock.RLock()
	if _, ok := iom.transfers[file]; ok {
		iom.transferLock.RUnlock()
		return fmt.Errorf("file already in flight")
	}
	iom.transferLock.RUnlock()

	// find the file somewhere in the mesh
	TID := genTID()
	c := make(chan *IOMMessage)
	err = iom.registerTID(TID, c)
	defer iom.unregisterTID(TID)

	if err != nil {
		// a collision in int64, we should tell someone about this
		log.Fatalln(err)
	}

	m := &IOMMessage{
		From:     iom.node.Name(),
		Type:     TYPE_INFO,
		Filename: file,
		TID:      TID,
	}
	recipients, err := iom.node.Broadcast(m)
	if err != nil {
		return err
	}
	if log.WillLog(log.DEBUG) {
		log.Debug("sent info request to %v nodes", len(recipients))
	}

	var info *IOMMessage
	var gotInfo bool
	// wait for n responses, or a timeout
	for i := 0; i < len(recipients); i++ {
		select {
		case resp := <-c:
			if log.WillLog(log.DEBUG) {
				log.Debugln("got response: ", resp)
			}
			if resp.ACK {
				if log.WillLog(log.DEBUG) {
					log.Debugln("got info from: ", resp.From)
				}
				info = resp
				gotInfo = true
			}
		case <-time.After(timeout):
			return fmt.Errorf("timeout")
		}
	}
	if !gotInfo {
		return fmt.Errorf("file not found")
	}

	// is this a single file or a directory/blob?
	if len(info.Glob) == 0 {
		if log.WillLog(log.DEBUG) {
			log.Debug("found file on node %v with %v parts", info.From, info.Part)
		}

		go iom.getParts(info.Filename, info.Part, info.Perm)
	} else {
		// call Get on each of the constituent files, queued in a random order

		// fisher-yates shuffle
		s := rand.NewSource(time.Now().UnixNano())
		r := rand.New(s)
		for i := int64(len(info.Glob)) - 1; i > 0; i-- {
			j := r.Int63n(i + 1)
			t := info.Glob[j]
			info.Glob[j] = info.Glob[i]
			info.Glob[i] = t
		}

		for _, v := range info.Glob {
			err := iom.Get(v)
			if err != nil {
				return err
			}
		}
	}

	return nil
}