blob: b6a831550b9d22e8025a289113a794391d33c703 [file] [log] [blame]
package namespace
import (
"errors"
"runtime"
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/options"
"veyron.io/veyron/veyron2/services/mounttable/types"
"veyron.io/veyron/veyron2/verror"
"veyron.io/veyron/veyron2/vlog"
)
func (ns *namespace) resolveAgainstMountTable(ctx context.T, client ipc.Client, e *naming.MountEntry) (*naming.MountEntry, error) {
// Try each server till one answers.
finalErr := errors.New("no servers to resolve query")
for _, s := range e.Servers {
name := naming.JoinAddressName(s.Server, e.Name)
// First check the cache.
if ne, err := ns.resolutionCache.lookup(name); err == nil {
vlog.VI(2).Infof("resolveAMT %s from cache -> %v", name, convertServersToStrings(ne.Servers, ne.Name))
return &ne, nil
}
// Not in cache, call the real server.
callCtx, _ := ctx.WithTimeout(callTimeout)
call, err := client.StartCall(callCtx, name, "ResolveStepX", nil, options.NoResolve(true))
if err != nil {
finalErr = err
vlog.VI(2).Infof("ResolveStep.StartCall %s failed: %s", name, err)
continue
}
var entry types.MountEntry
ierr := call.Finish(&entry, &err)
if ierr != nil {
// Internal/system error.
finalErr = ierr
vlog.VI(2).Infof("ResolveStep.Finish %s failed: %s", name, ierr)
continue
}
// If any replica says the name doesn't exist, return that fact.
if err != nil {
if verror.Equal(naming.ErrNoSuchName, err) || verror.Equal(naming.ErrNoSuchNameRoot, err) {
return nil, err
}
finalErr = err
vlog.VI(2).Infof("ResolveStep %s failed: %s", name, err)
continue
}
// Add result to cache.
ne := convertMountEntry(&entry)
ns.resolutionCache.remember(name, ne)
vlog.VI(2).Infof("resolveAMT %s -> %v", name, *ne)
return ne, nil
}
return nil, finalErr
}
func terminal(e *naming.MountEntry) bool {
for _, s := range e.Servers {
if !naming.Terminal(naming.JoinAddressName(s.Server, e.Name)) {
return false
}
}
return true
}
func makeTerminal(names []string) (ret []string) {
for _, name := range names {
ret = append(ret, naming.MakeTerminal(name))
}
return
}
// ResolveX implements veyron2/naming.Namespace.
func (ns *namespace) ResolveX(ctx context.T, name string) (*naming.MountEntry, error) {
defer vlog.LogCall()()
e := ns.rootMountEntry(name)
if vlog.V(2) {
_, file, line, _ := runtime.Caller(1)
vlog.Infof("ResolveX(%s) called from %s:%d", name, file, line)
vlog.Infof("ResolveX(%s) -> rootMountEntry %v", name, *e)
}
if len(e.Servers) == 0 {
return nil, naming.ErrNoMountTable
}
// Iterate walking through mount table servers.
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("ResolveX(%s) loop %v", name, *e)
if !e.MT || terminal(e) {
vlog.VI(1).Infof("ResolveX(%s) -> %v", name, *e)
return e, nil
}
var err error
curr := e
if e, err = ns.resolveAgainstMountTable(ctx, ns.rt.Client(), curr); err != nil {
// If the name could not be found in the mount table, return an error.
if verror.Equal(naming.ErrNoSuchNameRoot, err) {
err = naming.ErrNoSuchName
}
if verror.Equal(naming.ErrNoSuchName, err) {
vlog.VI(1).Infof("ResolveX(%s) -> (NoSuchName: %v)", name, curr)
return nil, err
}
// Any other failure (server not found, no ResolveStep
// method, etc.) are a sign that iterative resolution can
// stop.
vlog.VI(1).Infof("ResolveX(%s) -> %v", name, curr)
return curr, nil
}
}
return nil, naming.ErrResolutionDepthExceeded
}
// Resolve implements veyron2/naming.Namespace.
func (ns *namespace) Resolve(ctx context.T, name string) ([]string, error) {
defer vlog.LogCall()()
e, err := ns.ResolveX(ctx, name)
if err != nil {
return nil, err
}
return naming.ToStringSlice(e), nil
}
// ResolveToMountTableX implements veyron2/naming.Namespace.
func (ns *namespace) ResolveToMountTableX(ctx context.T, name string) (*naming.MountEntry, error) {
defer vlog.LogCall()()
e := ns.rootMountEntry(name)
if vlog.V(2) {
_, file, line, _ := runtime.Caller(1)
vlog.Infof("ResolveToMountTableX(%s) called from %s:%d", name, file, line)
vlog.Infof("ResolveToMountTableX(%s) -> rootNames %v", name, e)
}
if len(e.Servers) == 0 {
return nil, naming.ErrNoMountTable
}
last := e
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.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.MT || terminal(e) {
vlog.VI(1).Infof("ResolveToMountTableX(%s) -> %v", name, last)
return last, nil
}
if e, err = ns.resolveAgainstMountTable(ctx, ns.rt.Client(), e); err != nil {
if verror.Equal(naming.ErrNoSuchNameRoot, err) {
vlog.VI(1).Infof("ResolveToMountTableX(%s) -> %v (NoSuchRoot: %v)", name, last, curr)
return last, nil
}
if verror.Equal(naming.ErrNoSuchName, err) {
vlog.VI(1).Infof("ResolveToMountTableX(%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".
// TODO(p); make this less of a hack, make a specific verror code
// that means "we are up but don't implement what you are
// asking for".
if notAnMT(err) {
vlog.VI(1).Infof("ResolveToMountTableX(%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.
vlog.VI(1).Infof("ResolveToMountTableX(%s) -> %v", name, err)
return nil, err
}
last = curr
}
return nil, naming.ErrResolutionDepthExceeded
}
// ResolveToMountTable implements veyron2/naming.Namespace.
func (ns *namespace) ResolveToMountTable(ctx context.T, name string) ([]string, error) {
defer vlog.LogCall()()
e, err := ns.ResolveToMountTableX(ctx, name)
if err != nil {
return nil, err
}
return naming.ToStringSlice(e), nil
}
func finishUnresolve(call ipc.Call) ([]string, error) {
var newNames []string
var unresolveErr error
if err := call.Finish(&newNames, &unresolveErr); err != nil {
return nil, err
}
return newNames, unresolveErr
}
func unresolveAgainstServer(ctx context.T, client ipc.Client, names []string) ([]string, error) {
finalErr := errors.New("no servers to unresolve")
for _, name := range names {
name = naming.MakeTerminal(name)
callCtx, _ := ctx.WithTimeout(callTimeout)
call, err := client.StartCall(callCtx, name, "UnresolveStep", nil)
if err != nil {
finalErr = err
vlog.VI(2).Infof("StartCall %q.UnresolveStep() failed: %s", name, err)
continue
}
newNames, err := finishUnresolve(call)
if err == nil {
return newNames, nil
}
finalErr = err
vlog.VI(2).Infof("Finish %s failed: %v", name, err)
}
return nil, finalErr
}
// TODO(caprita): Unresolve currently picks the first responsive server as it
// goes up the ancestry line. This means that, if a service has several
// ancestors (each with potentially more than one replica), the first responsive
// replica of the first responsive ancestor is preferred. In particular,
// other branches are ignored. We need to figure out a desired policy for
// selecting the right branch (or should we return a representative of all
// branches?).
// Unesolve implements veyron2/naming.Namespace.
func (ns *namespace) Unresolve(ctx context.T, name string) ([]string, error) {
defer vlog.LogCall()()
vlog.VI(2).Infof("Unresolve %s", name)
names, err := ns.Resolve(ctx, name)
if err != nil {
return nil, err
}
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("Unresolve loop %s", names)
curr := names
if names, err = unresolveAgainstServer(ctx, ns.rt.Client(), names); err != nil {
return nil, err
}
if len(names) == 0 {
return curr, nil
}
}
return nil, naming.ErrResolutionDepthExceeded
}
// FlushCache flushes the most specific entry found for name. It returns true if anything was
// actually flushed.
func (ns *namespace) FlushCacheEntry(name string) bool {
defer vlog.LogCall()()
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.
n := naming.MakeTerminal(n)
if e, err := ns.resolutionCache.lookup(n); err == nil {
// Recurse.
for _, s := range e.Servers {
flushed = flushed || ns.FlushCacheEntry(naming.Join(s.Server, e.Name))
}
if !flushed {
// Forget the entry we just used.
ns.resolutionCache.forget([]string{naming.TrimSuffix(n, e.Name)})
flushed = true
}
}
}
return flushed
}