| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package namespace |
| |
| import ( |
| "runtime" |
| "strings" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/naming" |
| "v.io/v23/options" |
| "v.io/v23/rpc" |
| "v.io/v23/verror" |
| "v.io/x/ref/lib/apilog" |
| ) |
| |
| var ( |
| errNoServers = verror.Register(pkgPath+".errNoServers", verror.NoRetry, "{1} {2} No servers found to resolve query {_}") |
| ) |
| |
| // resolveAgainstMountTable asks each server in e.Servers that might be a mounttable to resolve e.Name. The requests |
| // are parallelized by the client rpc code. |
| func (ns *namespace) resolveAgainstMountTable(ctx *context.T, client rpc.Client, e *naming.MountEntry, opts ...rpc.CallOpt) (*naming.MountEntry, error) { |
| // Run through the server list looking for answers in the cache or servers that aren't mounttables. |
| change := false |
| for _, s := range e.Servers { |
| // If the server was not specified as an endpoint (perhaps as host:port) |
| // then we really don't know if this is a mounttable or not. Check the |
| // cache to see if we've tried in the recent past and it came back as not |
| // a mounttable. |
| if ns.resolutionCache.isNotMT(s.Server) { |
| change = true |
| continue |
| } |
| |
| // Check the cache. If its there, we're done. |
| n := naming.JoinAddressName(s.Server, e.Name) |
| if ne, err := ns.resolutionCache.lookup(ctx, n); err == nil { |
| ctx.VI(2).Infof("resolveAMT %s from cache -> %v", n, convertServersToStrings(ne.Servers, ne.Name)) |
| return &ne, nil |
| } |
| } |
| // We had at least one server that wasn't a mount table. Create a new mount entry without those servers. |
| if change { |
| ne := *e |
| ne.Servers = nil |
| for _, s := range e.Servers { |
| if !ns.resolutionCache.isNotMT(s.Server) { |
| ne.Servers = append(ne.Servers, s) |
| } |
| } |
| e = &ne |
| } |
| // If we have no servers to query, give up. |
| if len(e.Servers) == 0 { |
| ctx.VI(2).Infof("resolveAMT %s -> No servers", e.Name) |
| return nil, verror.New(errNoServers, ctx) |
| } |
| // We have preresolved the servers. Pass the mount entry to the call. |
| opts = append(opts, options.Preresolved{e}) |
| callCtx := ctx |
| if _, hasDeadline := ctx.Deadline(); !hasDeadline { |
| // Only set a per-call timeout if a deadline has not already |
| // been set. |
| var cancel func() |
| callCtx, cancel = withTimeout(ctx) |
| defer cancel() |
| } |
| entry := new(naming.MountEntry) |
| if err := client.Call(callCtx, e.Name, "ResolveStep", nil, []interface{}{entry}, opts...); err != nil { |
| // If it wasn't a mounttable remember that fact. The check for the __ is for |
| // the debugging hack in the local namespace of every server. That part never |
| // answers mounttable RPCs and shouldn't make us think this isn't a mounttable |
| // server. |
| if notAnMT(err) && !strings.HasPrefix(e.Name, "__") { |
| for _, s := range e.Servers { |
| ns.resolutionCache.setNotMT(s.Server) |
| } |
| } |
| return nil, err |
| } |
| // Add result to cache for each server that may have returned it. |
| for _, s := range e.Servers { |
| n := naming.JoinAddressName(s.Server, e.Name) |
| ns.resolutionCache.remember(ctx, n, entry) |
| } |
| ctx.VI(2).Infof("resolveAMT %s -> %v", e.Name, entry) |
| return entry, nil |
| } |
| |
| func terminal(e *naming.MountEntry) bool { |
| return len(e.Name) == 0 |
| } |
| |
| func hasEndpointPrefix(name string) bool { |
| if name == "" || naming.Rooted(name) { |
| return false |
| } |
| elems := strings.SplitN(name, "/", 2) |
| _, err := naming.ParseEndpoint(elems[0]) |
| return err == nil |
| } |
| |
| // Resolve implements v.io/v23/naming.Namespace. |
| func (ns *namespace) Resolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) { |
| defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| |
| e, err := ns.resolveInternal(ctx, name, opts...) |
| // If the resolution failed and it appears that the name starts with an |
| // endpoint but was mistakenly passed in un-rooted, add a sub error to |
| // point that out to the client. |
| |
| // TODO(caprita): Consider doing this also for ResolveShallow and |
| // ResolveToMountTable. |
| if err != nil && hasEndpointPrefix(name) { |
| verrorE := verror.Convert(verror.IDAction{}, ctx, err) |
| err = verror.AddSubErrs(verrorE, nil, verror.SubErr{"Did you mean", verror.E{Msg: "/" + name}, verror.Print}) |
| } |
| return e, err |
| } |
| |
| func (ns *namespace) resolveInternal(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) { |
| // If caller supplied a mount entry, use it. |
| e, skipResolution := preresolved(opts) |
| if e != nil { |
| return e, nil |
| } |
| // Expand any relative name. |
| e, _ = ns.rootMountEntry(name, opts...) |
| if ctx.V(2) { |
| _, file, line, _ := runtime.Caller(1) |
| ctx.Infof("Resolve(%s) called from %s:%d", name, file, line) |
| ctx.Infof("Resolve(%s) -> rootEntry %v", name, *e) |
| } |
| // If caller didn't want resolution, use expanded name. |
| if skipResolution { |
| return e, nil |
| } |
| if len(e.Servers) == 0 { |
| return nil, verror.New(naming.ErrNoSuchName, ctx, name) |
| } |
| client := v23.GetClient(ctx) |
| callOpts := getCallOpts(opts) |
| |
| // Iterate walking through mount table servers. |
| for remaining := ns.maxResolveDepth; remaining > 0; remaining-- { |
| if ctx.V(2) { |
| ctx.Infof("Resolve(%s) loop %v", name, *e) |
| } |
| if !e.ServesMountTable { |
| if ctx.V(1) { |
| ctx.Infof("Resolve(%s) -> %v", name, *e) |
| } |
| return e, nil |
| } |
| var err error |
| curr := e |
| if e, err = ns.resolveAgainstMountTable(ctx, client, curr, callOpts...); err != nil { |
| // Lots of reasons why another error can happen. We are trying |
| // to single out "this isn't a mount table". |
| if notAnMT(err) { |
| ctx.VI(1).Infof("Resolve(%s) -> %v", name, curr) |
| return curr, nil |
| } |
| if verror.ErrorID(err) == naming.ErrNoSuchNameRoot.ID { |
| err = verror.New(naming.ErrNoSuchName, ctx, name) |
| } |
| ctx.VI(1).Infof("Resolve(%s) -> (%s: %v)", err, name, curr) |
| return nil, err |
| } |
| } |
| return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx) |
| } |
| |
| // ShallowResolve implements v.io/v23/naming.Namespace. |
| func (ns *namespace) ShallowResolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) { |
| defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| |
| // Find the containing mount table. |
| me, err := ns.ResolveToMountTable(ctx, name, opts...) |
| if err != nil { |
| return nil, err |
| } |
| if terminal(me) { |
| return me, nil |
| } |
| |
| // Resolve the entry directly. |
| client := v23.GetClient(ctx) |
| entry := new(naming.MountEntry) |
| err = client.Call(ctx, name, "ResolveStep", nil, []interface{}{entry}, append(getCallOpts(opts), options.Preresolved{me})...) |
| return entry, err |
| } |
| |
| // ResolveToMountTable implements v.io/v23/naming.Namespace. |
| func (ns *namespace) ResolveToMountTable(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) { |
| defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| e, _ := ns.rootMountEntry(name, opts...) |
| if ctx.V(2) { |
| _, file, line, _ := runtime.Caller(1) |
| ctx.Infof("ResolveToMountTable(%s) called from %s:%d", name, file, line) |
| ctx.Infof("ResolveToMountTable(%s) -> rootNames %v", name, e) |
| } |
| if len(e.Servers) == 0 { |
| return nil, verror.New(naming.ErrNoMountTable, ctx) |
| } |
| callOpts := getCallOpts(opts) |
| client := v23.GetClient(ctx) |
| last := e |
| for remaining := ns.maxResolveDepth; remaining > 0; remaining-- { |
| ctx.VI(2).Infof("ResolveToMountTable(%s) loop %v", name, e) |
| var err error |
| curr := e |
| // If the next name to resolve doesn't point to a mount table, we're done. |
| if !e.ServesMountTable || terminal(e) { |
| ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, last) |
| return last, nil |
| } |
| if e, err = ns.resolveAgainstMountTable(ctx, client, e, callOpts...); err != nil { |
| if verror.ErrorID(err) == naming.ErrNoSuchNameRoot.ID { |
| ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v (NoSuchRoot: %v)", name, last, curr) |
| return last, nil |
| } |
| if verror.ErrorID(err) == naming.ErrNoSuchName.ID { |
| ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v (NoSuchName: %v)", name, curr, curr) |
| return curr, nil |
| } |
| // Lots of reasons why another error can happen. We are trying |
| // to single out "this isn't a mount table". |
| if notAnMT(err) { |
| ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, last) |
| return last, nil |
| } |
| // TODO(caprita): If the server is unreachable for |
| // example, we may still want to return its parent |
| // mounttable rather than an error. |
| ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, err) |
| return nil, err |
| } |
| last = curr |
| } |
| return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx) |
| } |
| |
| // FlushCache flushes the most specific entry found for name. It returns true if anything was |
| // actually flushed. |
| func (ns *namespace) FlushCacheEntry(ctx *context.T, name string) bool { |
| defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| flushed := false |
| for _, n := range ns.rootName(name) { |
| // Walk the cache as we would in a resolution. Unlike a resolution, we have to follow |
| // all branches since we want to flush all entries at which we might end up whereas in a resolution, |
| // we stop with the first branch that works. |
| if e, err := ns.resolutionCache.lookup(ctx, n); err == nil { |
| // Recurse. |
| for _, s := range e.Servers { |
| flushed = flushed || ns.FlushCacheEntry(ctx, naming.Join(s.Server, e.Name)) |
| } |
| if !flushed { |
| // Forget the entry we just used. |
| ns.resolutionCache.forget(ctx, []string{naming.TrimSuffix(n, e.Name)}) |
| flushed = true |
| } |
| } |
| } |
| return flushed |
| } |
| |
| func preresolved(opts []naming.NamespaceOpt) (*naming.MountEntry, bool) { |
| for _, o := range opts { |
| if v, ok := o.(options.Preresolved); ok { |
| return v.Resolution, true |
| } |
| } |
| return nil, false |
| } |