blob: e1c4109fe6d06b77d4312244f600c20d6e0ad18f [file] [log] [blame]
package namespace
import (
"errors"
"fmt"
"runtime"
"veyron.io/veyron/veyron2"
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/options"
verror "veyron.io/veyron/veyron2/verror2"
"veyron.io/veyron/veyron2/vlog"
)
func (ns *namespace) resolveAgainstMountTable(ctx context.T, client ipc.Client, e *naming.MountEntry, pattern string, opts ...ipc.CallOpt) (*naming.MountEntry, error) {
// Try each server till one answers.
finalErr := errors.New("no servers to resolve query")
for _, s := range e.Servers {
var pattern_and_name string
name := naming.JoinAddressName(s.Server, e.Name)
if pattern != "" {
pattern_and_name = naming.JoinAddressName(s.Server, fmt.Sprintf("[%s]%s", pattern, e.Name))
} else {
pattern_and_name = 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, pattern_and_name, "ResolveStepX", nil, append(opts, options.NoResolve(true))...)
if err != nil {
finalErr = err
vlog.VI(2).Infof("ResolveStep.StartCall %s failed: %s", name, err)
continue
}
var entry naming.VDLMountEntry
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.Is(err, naming.ErrNoSuchName.ID) || verror.Is(err, naming.ErrNoSuchNameRoot.ID) {
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 {
return len(e.Name) == 0
}
// ResolveX implements veyron2/naming.Namespace.
func (ns *namespace) ResolveX(ctx context.T, name string, opts ...naming.ResolveOpt) (*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, verror.Make(naming.ErrNoSuchName, ctx, name)
}
pattern := getRootPattern(opts)
client := veyron2.RuntimeFromContext(ctx).Client()
var callOpts []ipc.CallOpt
for _, opt := range opts {
if callOpt, ok := opt.(ipc.CallOpt); ok {
callOpts = append(callOpts, callOpt)
}
}
// 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.ServesMountTable() || 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, client, curr, pattern, 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) {
vlog.VI(1).Infof("ResolveX(%s) -> %v", name, curr)
return curr, nil
}
if verror.Is(err, naming.ErrNoSuchNameRoot.ID) {
err = verror.Make(naming.ErrNoSuchName, ctx, name)
}
vlog.VI(1).Infof("ResolveX(%s) -> (%s: %v)", err, name, curr)
return nil, err
}
pattern = ""
}
return nil, verror.Make(naming.ErrResolutionDepthExceeded, ctx)
}
// Resolve implements veyron2/naming.Namespace.
func (ns *namespace) Resolve(ctx context.T, name string, opts ...naming.ResolveOpt) ([]string, error) {
defer vlog.LogCall()()
e, err := ns.ResolveX(ctx, name, opts...)
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, opts ...naming.ResolveOpt) (*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, verror.Make(naming.ErrNoMountTable, ctx)
}
pattern := getRootPattern(opts)
client := veyron2.RuntimeFromContext(ctx).Client()
last := e
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("ResolveToMountTableX(%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) {
vlog.VI(1).Infof("ResolveToMountTableX(%s) -> %v", name, last)
return last, nil
}
if e, err = ns.resolveAgainstMountTable(ctx, client, e, pattern); err != nil {
if verror.Is(err, naming.ErrNoSuchNameRoot.ID) {
vlog.VI(1).Infof("ResolveToMountTableX(%s) -> %v (NoSuchRoot: %v)", name, last, curr)
return last, nil
}
if verror.Is(err, naming.ErrNoSuchName.ID) {
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".
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
pattern = ""
}
return nil, verror.Make(naming.ErrResolutionDepthExceeded, ctx)
}
// ResolveToMountTable implements veyron2/naming.Namespace.
func (ns *namespace) ResolveToMountTable(ctx context.T, name string, opts ...naming.ResolveOpt) ([]string, error) {
defer vlog.LogCall()()
e, err := ns.ResolveToMountTableX(ctx, name, opts...)
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
}
// TODO(caprita): UnresolveStep no longer exists.
func unresolveAgainstServer(ctx context.T, client ipc.Client, names []string) ([]string, error) {
finalErr := errors.New("no servers to unresolve")
for _, name := range names {
callCtx, _ := ctx.WithTimeout(callTimeout)
call, err := client.StartCall(callCtx, name, "UnresolveStep", nil, options.NoResolve(true))
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
}
client := veyron2.RuntimeFromContext(ctx).Client()
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("Unresolve loop %s", names)
curr := names
if names, err = unresolveAgainstServer(ctx, client, names); err != nil {
return nil, err
}
if len(names) == 0 {
return curr, nil
}
}
return nil, verror.Make(naming.ErrResolutionDepthExceeded, ctx)
}
// 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.
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
}
func getRootPattern(opts []naming.ResolveOpt) string {
for _, opt := range opts {
if pattern, ok := opt.(naming.RootBlessingPatternOpt); ok {
return string(pattern)
}
}
return ""
}