blob: 4b3d296595bb30399ef92e5b87fff480fcd6a70d [file] [log] [blame]
package namespace
import (
"container/list"
"io"
"strings"
"time"
"veyron.io/veyron/veyron/lib/glob"
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/vlog"
)
const mountTableGlobReplyStreamLength = 100
type queuedEntry struct {
me *naming.MountEntry
depth int // number of mount tables traversed recursively
}
// 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
func (ns *namespace) globAtServer(ctx context.T, qe *queuedEntry, pattern *glob.Glob, l *list.List) error {
server := qe.me
pstr := pattern.String()
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 {
// If the pattern is finished (so we're only querying about the root on the
// remote server) and the server is not another MT, then we needn't send the
// query on since we know the server will not supply a new address for the
// current name.
if pattern.Finished() {
_, n := naming.SplitAddressName(s.Server)
if strings.HasPrefix(n, "//") {
return nil
}
}
// Don't further resolve s.Server.
s := naming.MakeTerminal(s.Server)
callCtx, _ := ctx.WithTimeout(callTimeout)
client := ns.rt.Client()
call, err := client.StartCall(callCtx, s, "Glob", []interface{}{pstr})
if err != nil {
lastErr = err
continue // try another instance
}
// At this point we're commited to a server since it answered tha call.
for {
var e mountEntry
err := call.Recv(&e)
if err == io.EOF {
return nil
}
if err != nil {
return err
}
// 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.
x := &queuedEntry{
me: &naming.MountEntry{
Name: e.Name,
Servers: convertServers(e.Servers),
},
depth: qe.depth,
}
// x.depth is the number of severs we've walked through since we've gone
// recursive (i.e. with pattern length of 0).
if pattern.Len() == 0 {
if x.depth++; x.depth > ns.maxRecursiveGlobDepth {
continue
}
}
l.PushBack(x)
}
if err := call.Finish(); err != nil {
return err
}
return nil
}
return lastErr
}
// Glob implements naming.MountTable.Glob.
func (ns *namespace) Glob(ctx context.T, pattern string) (chan naming.MountEntry, error) {
defer vlog.LogCall()()
root, globPattern := naming.SplitAddressName(pattern)
g, err := glob.Parse(globPattern)
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, "/")
prefix := ""
if len(root) != 0 {
prefix = naming.JoinAddressName(root, prefix)
}
// 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(ctx, 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(ctx context.T, servers []string, prefix string, pattern *glob.Glob, reply chan naming.MountEntry) {
defer close(reply)
// As we encounter new mount tables while traversing the Glob, we add them to the list 'l'. The loop below
// traverses this list removing a mount table each time and calling globAtServer to perform a glob at that
// server. globAtServer will send on 'reply' any terminal entries that match the glob and add any new mount
// tables to be traversed to the list 'l'.
l := list.New()
l.PushBack(&queuedEntry{me: &naming.MountEntry{Name: "", Servers: convertStringsToServers(servers)}})
atRoot := true
// Perform a breadth first search of the name graph.
for le := l.Front(); le != nil; le = l.Front() {
l.Remove(le)
e := le.Value.(*queuedEntry)
// Get the pattern elements below the current path.
suffix := pattern.Split(depth(e.me.Name))
// Perform a glob at the server.
err := ns.globAtServer(ctx, 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.me
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 && !atRoot {
x := *e.me
x.Name = naming.Join(prefix, x.Name)
reply <- x
}
atRoot = false
}
}