blob: ddc5ae607dd4e8fd012931addb60897819c08fc3 [file] [log] [blame]
package mounttable
import (
"container/list"
"io"
"strings"
"time"
"veyron/lib/glob"
"veyron2/naming"
"veyron2/vlog"
)
const mountTableGlobReplyStreamLength = 100
// globAtServer performs a Glob at a single server and adds any results to the list. Paramters are:
// server the server to perform the glob at. This may include multiple names for different
// instances of the same server.
// pelems the pattern to match relative to the mounted subtree.
// l the list to add results to.
// recursive true to continue below the matched pattern
// We return a bool foundRoot which indicates whether the empty name "" was found on a target server.
func (ns *namespace) globAtServer(server *naming.MountEntry, pattern *glob.Glob, l *list.List) (bool, error) {
pstr := pattern.String()
foundRoot := false
vlog.VI(2).Infof("globAtServer(%v, %v)", *server, pstr)
var lastErr error
// Trying each instance till we get one that works.
for _, s := range server.Servers {
mtServers, err := ns.ResolveToMountTable(s.Server)
if err != nil {
lastErr = err
continue
}
for _, s := range mtServers {
client := ns.rt.Client()
call, err := client.StartCall(ns.rt.TODOContext(), s, "Glob", []interface{}{pstr}, callTimeout)
if err != nil {
lastErr = err
continue // try another instance
}
// At this point we have a server that can handle the RPC.
for {
var e mountEntry
err := call.Recv(&e)
if err == io.EOF {
return foundRoot, nil
}
if err != nil {
return foundRoot, err
}
if e.Name == "" {
foundRoot = true
}
// Prefix the results with the path of the mount point.
e.Name = naming.Join(server.Name, e.Name)
// Convert to the ever so slightly different name.MountTable version of a MountEntry
// and add it to the list.
l.PushBack(&naming.MountEntry{
Name: e.Name,
Servers: convertServers(e.Servers),
})
}
if err := call.Finish(); err != nil {
return foundRoot, err
}
return foundRoot, nil
}
}
return foundRoot, lastErr
}
// Glob implements naming.MountTable.Glob.
func (ns *namespace) Glob(pattern string) (chan naming.MountEntry, error) {
g, err := glob.Parse(pattern)
if err != nil {
return nil, err
}
// Add constant components of pattern to the servers' addresses and
// to the prefix.
var prefixElements []string
prefixElements, g = g.SplitFixedPrefix()
prefix := strings.Join(prefixElements, "/")
// Start a thread to get the results and return the reply channel to the caller.
servers := ns.rootName(prefix)
if len(servers) == 0 {
return nil, naming.ErrNoMountTable
}
reply := make(chan naming.MountEntry, 100)
go ns.globLoop(servers, prefix, g, reply)
return reply, nil
}
func convertStringsToServers(servers []string) (ret []naming.MountedServer) {
for _, s := range servers {
ret = append(ret, naming.MountedServer{Server: s})
}
return
}
// TODO(p): I may just give up and assume that these two will always be the same. For
// now this lets me make the RPC interface and the model's MountTable structs be arbitrarily
// different.
func convertServers(servers []mountedServer) []naming.MountedServer {
var reply []naming.MountedServer
for _, s := range servers {
reply = append(reply, naming.MountedServer{Server: s.Server, TTL: time.Duration(s.TTL) * time.Second})
}
return reply
}
// depth returns the directory depth of a given name.
func depth(name string) int {
name = strings.Trim(name, "/")
if name == "" {
return 0
}
return strings.Count(name, "/") - strings.Count(name, "//") + 1
}
func (ns *namespace) globLoop(servers []string, prefix string, pattern *glob.Glob, reply chan naming.MountEntry) {
defer close(reply)
l := list.New()
l.PushBack(&naming.MountEntry{Name: "", Servers: convertStringsToServers(servers)})
// Perform a breadth first search of the name graph.
for le := l.Front(); le != nil; le = l.Front() {
l.Remove(le)
e := le.Value.(*naming.MountEntry)
// Get the pattern elements below the current path.
suffix := pattern.Split(depth(e.Name))
// Perform a glob at the server.
foundRoot, err := ns.globAtServer(e, suffix, l)
// We want to output this entry if:
// 1. There was a real error, we return whatever name gave us the error.
if err != nil && !notAnMT(err) {
x := *e
x.Name = naming.Join(prefix, x.Name)
x.Error = err
reply <- x
}
// 2. The current name fullfills the pattern and further servers did not respond
// with "". That is, we want to prefer foo/ over foo.
if suffix.Len() == 0 && !foundRoot {
x := *e
x.Name = naming.Join(prefix, x.Name)
reply <- x
}
}
}