Merge "veyron/services/mgmt/node/impl: identity management for child apps (agent-less)."
diff --git a/runtimes/google/ipc/glob.go b/runtimes/google/ipc/glob.go
new file mode 100644
index 0000000..3dfaa3b
--- /dev/null
+++ b/runtimes/google/ipc/glob.go
@@ -0,0 +1,155 @@
+package ipc
+
+import (
+ "strings"
+
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/naming"
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/services/mounttable/types"
+ "veyron.io/veyron/veyron2/verror"
+ "veyron.io/veyron/veyron2/vlog"
+
+ "veyron.io/veyron/veyron/lib/glob"
+)
+
+// globInternal handles ALL the Glob requests received by a server and
+// constructs a response from the state of internal server objects and the
+// service objects.
+//
+// Internal objects exist only at the root of the server and have a name that
+// starts with a double underscore ("__"). They are only visible in the Glob
+// response if the double underscore is explicitly part of the pattern, e.g.
+// "".Glob("__*/*"), or "".Glob("__debug/...").
+//
+// Service objects may choose to implement either VAllGlobber or
+// VChildrenGlobber. VAllGlobber is more flexible, but VChildrenGlobber is
+// simpler to implement and less prone to errors.
+//
+// If objects implement VAllGlobber, it must be able to handle recursive pattern
+// for the entire namespace below the receiver object, i.e. "a/b".Glob("...")
+// must return the name of all the objects under "a/b".
+//
+// If they implement VChildrenGlobber, it provides a list of the receiver's
+// immediate children names, or a non-nil error if the receiver doesn't exist.
+//
+// globInternal constructs the Glob response by internally accessing the
+// VAllGlobber or VChildrenGlobber interface of objects as many times as needed.
+//
+// Before accessing an object, globInternal ensures that the requester is
+// authorized to access it. Internal objects require either security.DebugLabel
+// or security.MonitoringLabel. Service objects require security.ResolveLabel.
+type globInternal struct {
+ fs *flowServer
+ receiver string
+}
+
+// The maximum depth of recursion in Glob. We only count recursion levels
+// associated with a recursive glob pattern, e.g. a pattern like "..." will be
+// allowed to recurse up to 10 levels, but "*/*/*/*/..." will go up to 14
+// levels.
+const maxRecursiveGlobDepth = 10
+
+func (i *globInternal) Glob(call ipc.ServerCall, pattern string) error {
+ vlog.VI(3).Infof("ipc Glob: Incoming request: %q.Glob(%q)", i.receiver, pattern)
+ g, err := glob.Parse(pattern)
+ if err != nil {
+ return err
+ }
+ var disp ipc.Dispatcher
+ if naming.IsReserved(i.receiver) || (i.receiver == "" && naming.IsReserved(pattern)) {
+ disp = i.fs.reservedOpt.Dispatcher
+ i.fs.tags = []interface{}{security.DebugLabel | security.MonitoringLabel}
+ } else {
+ disp = i.fs.disp
+ i.fs.tags = []interface{}{security.ResolveLabel}
+ }
+ if disp == nil {
+ return verror.NoExistf("ipc: Glob is not implemented by %q", i.receiver)
+ }
+ return i.globStep(call, disp, "", g, 0)
+}
+
+func (i *globInternal) globStep(call ipc.ServerCall, disp ipc.Dispatcher, name string, g *glob.Glob, depth int) error {
+ suffix := naming.Join(i.receiver, name)
+ if depth > maxRecursiveGlobDepth {
+ err := verror.Internalf("ipc: Glob exceeded its recursion limit (%d): %q", maxRecursiveGlobDepth, suffix)
+ vlog.Error(err)
+ return err
+ }
+ invoker, auth, verr := lookupInvoker(disp, suffix, ipc.GlobMethod)
+ if verr != nil {
+ return verr
+ }
+ if invoker == nil {
+ return verror.NoExistf("ipc: invoker not found for %q.%s", suffix, ipc.GlobMethod)
+ }
+
+ // Verify that that requester is authorized for the current object.
+ i.fs.suffix = suffix
+ if err := i.fs.authorize(auth); err != nil {
+ return err
+ }
+
+ // If the object implements both VAllGlobber and VChildrenGlobber, we'll
+ // use VAllGlobber.
+ gs := invoker.VGlob()
+ if gs != nil && gs.VAllGlobber != nil {
+ vlog.VI(3).Infof("ipc Glob: %q implements VAllGlobber", suffix)
+ childCall := &localServerCall{ServerCall: call, basename: name}
+ return gs.VAllGlobber.Glob(childCall, g.String())
+ }
+ if gs != nil && gs.VChildrenGlobber != nil {
+ vlog.VI(3).Infof("ipc Glob: %q implements VChildrenGlobber", suffix)
+ children, err := gs.VChildrenGlobber.VGlobChildren()
+ if err != nil {
+ return nil
+ }
+ if g.Len() == 0 {
+ call.Send(types.MountEntry{Name: name})
+ }
+ if g.Finished() {
+ return nil
+ }
+ if g.Len() == 0 {
+ // This is a recursive pattern. Make sure we don't recurse forever.
+ depth++
+ }
+ for _, child := range children {
+ if len(child) == 0 || strings.Contains(child, "/") {
+ vlog.Errorf("ipc: %q.VGlobChildren() returned an invalid child name: %q", suffix, child)
+ continue
+ }
+ if ok, _, left := g.MatchInitialSegment(child); ok {
+ next := naming.Join(name, child)
+ if err := i.globStep(call, disp, next, left, depth); err != nil {
+ vlog.VI(1).Infof("ipc Glob: globStep(%q, %q): %v", next, left, err)
+ }
+ }
+ }
+ return nil
+ }
+
+ vlog.VI(3).Infof("ipc Glob: %q implements neither VAllGlobber nor VChildrenGlobber", suffix)
+ return verror.NoExistf("ipc: Glob is not implemented by %q", suffix)
+}
+
+// An ipc.ServerCall that prepends a name to all the names in the streamed
+// MountEntry objects.
+type localServerCall struct {
+ ipc.ServerCall
+ basename string
+}
+
+var _ ipc.ServerCall = (*localServerCall)(nil)
+var _ ipc.Stream = (*localServerCall)(nil)
+var _ ipc.ServerContext = (*localServerCall)(nil)
+
+func (c *localServerCall) Send(v interface{}) error {
+ me, ok := v.(types.MountEntry)
+ if !ok {
+ return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
+ }
+ me.Name = naming.Join(c.basename, me.Name)
+ return c.ServerCall.Send(me)
+}
diff --git a/runtimes/google/ipc/glob_test.go b/runtimes/google/ipc/glob_test.go
new file mode 100644
index 0000000..5af6586
--- /dev/null
+++ b/runtimes/google/ipc/glob_test.go
@@ -0,0 +1,281 @@
+package ipc_test
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+
+ "veyron.io/veyron/veyron2"
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/naming"
+ "veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/services/mounttable"
+ "veyron.io/veyron/veyron2/services/mounttable/types"
+
+ "veyron.io/veyron/veyron/lib/glob"
+ "veyron.io/veyron/veyron/profiles"
+)
+
+func startServer(rt veyron2.Runtime, tree *node) (string, func(), error) {
+ server, err := rt.NewServer()
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to start debug server: %v", err)
+ }
+ endpoint, err := server.Listen(profiles.LocalListenSpec)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to listen: %v", err)
+ }
+ if err := server.ServeDispatcher("", &disp{tree}); err != nil {
+ return "", nil, err
+ }
+ ep := endpoint.String()
+ return ep, func() { server.Stop() }, nil
+}
+
+func TestGlob(t *testing.T) {
+ runtime := rt.Init()
+ defer runtime.Cleanup()
+
+ namespace := []string{
+ "a/b/c1/d1",
+ "a/b/c1/d2",
+ "a/b/c2/d1",
+ "a/b/c2/d2",
+ "a/x/y/z",
+ }
+ tree := newNode()
+ for _, p := range namespace {
+ tree.find(strings.Split(p, "/"), true)
+ }
+
+ ep, stop, err := startServer(runtime, tree)
+ if err != nil {
+ t.Fatalf("startServer: %v", err)
+ }
+ defer stop()
+
+ testcases := []struct {
+ name, pattern string
+ expected []string
+ }{
+ {"", "...", []string{
+ "",
+ "a",
+ "a/b",
+ "a/b/c1",
+ "a/b/c1/d1",
+ "a/b/c1/d2",
+ "a/b/c2",
+ "a/b/c2/d1",
+ "a/b/c2/d2",
+ "a/x",
+ "a/x/y",
+ "a/x/y/z",
+ }},
+ {"a", "...", []string{
+ "",
+ "b",
+ "b/c1",
+ "b/c1/d1",
+ "b/c1/d2",
+ "b/c2",
+ "b/c2/d1",
+ "b/c2/d2",
+ "x",
+ "x/y",
+ "x/y/z",
+ }},
+ {"a/b", "...", []string{
+ "",
+ "c1",
+ "c1/d1",
+ "c1/d2",
+ "c2",
+ "c2/d1",
+ "c2/d2",
+ }},
+ {"a/b/c1", "...", []string{
+ "",
+ "d1",
+ "d2",
+ }},
+ {"a/b/c1/d1", "...", []string{
+ "",
+ }},
+ {"a/x", "...", []string{
+ "",
+ "y",
+ "y/z",
+ }},
+ {"a/x/y", "...", []string{
+ "",
+ "z",
+ }},
+ {"a/x/y/z", "...", []string{
+ "",
+ }},
+ {"", "", []string{""}},
+ {"", "*", []string{"a"}},
+ {"a", "", []string{""}},
+ {"a", "*", []string{"b", "x"}},
+ {"a/b", "", []string{""}},
+ {"a/b", "*", []string{"c1", "c2"}},
+ {"a/b/c1", "", []string{""}},
+ {"a/b/c1", "*", []string{"d1", "d2"}},
+ {"a/b/c1/d1", "*", []string{}},
+ {"a/b/c1/d1", "", []string{""}},
+ {"a", "*/c?", []string{"b/c1", "b/c2"}},
+ {"a", "*/*", []string{"b/c1", "b/c2", "x/y"}},
+ {"a", "*/*/*", []string{"b/c1/d1", "b/c1/d2", "b/c2/d1", "b/c2/d2", "x/y/z"}},
+ {"a/x", "*/*", []string{"y/z"}},
+ {"bad", "", []string{}},
+ {"a/bad", "", []string{}},
+ {"a/b/bad", "", []string{}},
+ {"a/b/c1/bad", "", []string{}},
+ {"a/x/bad", "", []string{}},
+ {"a/x/y/bad", "", []string{}},
+ // muah is an infinite space to test rescursion limit.
+ {"muah", "*", []string{"ha"}},
+ {"muah", "*/*", []string{"ha/ha"}},
+ {"muah", "*/*/*/*/*/*/*/*/*/*/*/*", []string{"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha"}},
+ {"muah", "...", []string{
+ "",
+ "ha",
+ "ha/ha",
+ "ha/ha/ha",
+ "ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha/ha/ha/ha/ha",
+ "ha/ha/ha/ha/ha/ha/ha/ha/ha/ha",
+ }},
+ }
+ for _, tc := range testcases {
+ c := mounttable.GlobbableClient(naming.JoinAddressName(ep, tc.name))
+
+ stream, err := c.Glob(runtime.NewContext(), tc.pattern)
+ if err != nil {
+ t.Fatalf("Glob failed: %v", err)
+ }
+ results := []string{}
+ iterator := stream.RecvStream()
+ for iterator.Advance() {
+ results = append(results, iterator.Value().Name)
+ }
+ sort.Strings(results)
+ if !reflect.DeepEqual(results, tc.expected) {
+ t.Errorf("unexpected result for (%q, %q). Got %q, want %q", tc.name, tc.pattern, results, tc.expected)
+ }
+ if err := iterator.Err(); err != nil {
+ t.Errorf("unexpected stream error for %q: %v", tc.name, err)
+ }
+ if err := stream.Finish(); err != nil {
+ t.Errorf("Finish failed for %q: %v", tc.name, err)
+ }
+ }
+}
+
+type disp struct {
+ tree *node
+}
+
+func (d *disp) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
+ elems := strings.Split(suffix, "/")
+ if len(elems) != 0 && elems[0] == "muah" {
+ // Infinite space. Each node has one child named "ha".
+ return ipc.VChildrenGlobberInvoker("ha"), nil, nil
+
+ }
+ if len(elems) <= 2 || (elems[0] == "a" && elems[1] == "x") {
+ return &vChildrenObject{d.tree, elems}, nil, nil
+ }
+ return &globObject{d.tree, elems}, nil, nil
+}
+
+type globObject struct {
+ n *node
+ suffix []string
+}
+
+func (o *globObject) Glob(call ipc.ServerCall, pattern string) error {
+ g, err := glob.Parse(pattern)
+ if err != nil {
+ return err
+ }
+ n := o.n.find(o.suffix, false)
+ if n == nil {
+ return nil
+ }
+ o.globLoop(call, "", g, n)
+ return nil
+}
+
+func (o *globObject) globLoop(call ipc.ServerCall, name string, g *glob.Glob, n *node) {
+ if g.Len() == 0 {
+ call.Send(types.MountEntry{Name: name})
+ }
+ if g.Finished() {
+ return
+ }
+ for leaf, child := range n.children {
+ if ok, _, left := g.MatchInitialSegment(leaf); ok {
+ o.globLoop(call, naming.Join(name, leaf), left, child)
+ }
+ }
+}
+
+type vChildrenObject struct {
+ n *node
+ suffix []string
+}
+
+func (o *vChildrenObject) VGlobChildren() ([]string, error) {
+ n := o.n.find(o.suffix, false)
+ if n == nil {
+ return nil, fmt.Errorf("object does not exist")
+ }
+ children := make([]string, len(n.children))
+ index := 0
+ for child, _ := range n.children {
+ children[index] = child
+ index++
+ }
+ return children, nil
+}
+
+type node struct {
+ children map[string]*node
+}
+
+func newNode() *node {
+ return &node{make(map[string]*node)}
+}
+
+func (n *node) find(names []string, create bool) *node {
+ if len(names) == 1 && names[0] == "" {
+ return n
+ }
+ for {
+ if len(names) == 0 {
+ return n
+ }
+ if next, ok := n.children[names[0]]; ok {
+ n = next
+ names = names[1:]
+ continue
+ }
+ if create {
+ nn := newNode()
+ n.children[names[0]] = nn
+ n = nn
+ names = names[1:]
+ continue
+ }
+ return nil
+ }
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 18d8684..42172dd 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -16,13 +16,11 @@
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/options"
"veyron.io/veyron/veyron2/security"
- mttypes "veyron.io/veyron/veyron2/services/mounttable/types"
"veyron.io/veyron/veyron2/verror"
"veyron.io/veyron/veyron2/vlog"
"veyron.io/veyron/veyron2/vom"
"veyron.io/veyron/veyron2/vtrace"
- "veyron.io/veyron/veyron/lib/glob"
"veyron.io/veyron/veyron/lib/netstate"
"veyron.io/veyron/veyron/runtimes/google/lib/publisher"
inaming "veyron.io/veyron/veyron/runtimes/google/naming"
@@ -809,6 +807,7 @@
return nil, verr
}
fs.method = req.Method
+ fs.suffix = req.Suffix
// TODO(mattr): Currently this allows users to trigger trace collection
// on the server even if they will not be allowed to collect the
@@ -861,14 +860,13 @@
fs.discharges[d.ID()] = d
}
// Lookup the invoker.
- invoker, auth, suffix, verr := fs.lookup(req.Suffix, req.Method)
- fs.suffix = suffix // with leading /'s stripped
+ invoker, auth, verr := fs.lookup(&fs.suffix, &fs.method)
if verr != nil {
return nil, verr
}
// Prepare invoker and decode args.
numArgs := int(req.NumPosArgs)
- argptrs, tags, err := invoker.Prepare(req.Method, numArgs)
+ argptrs, tags, err := invoker.Prepare(fs.method, numArgs)
fs.tags = tags
if err != nil {
return nil, verror.Makef(verror.ErrorID(err), "%s: name: %q", err, req.Suffix)
@@ -893,42 +891,40 @@
fs.allowDebug = fs.authorizeForDebug(auth) == nil
}
- results, err := invoker.Invoke(req.Method, fs, argptrs)
- fs.server.stats.record(req.Method, time.Since(fs.starttime))
+ results, err := invoker.Invoke(fs.method, fs, argptrs)
+ fs.server.stats.record(fs.method, time.Since(fs.starttime))
return results, verror.Convert(err)
}
// lookup returns the invoker and authorizer responsible for serving the given
// name and method. The name is stripped of any leading slashes. If it begins
// with ipc.DebugKeyword, we use the internal debug dispatcher to look up the
-// invoker. Otherwise, and we use the server's dispatcher. The (stripped) name
-// and dispatch suffix are also returned.
-func (fs *flowServer) lookup(name, method string) (ipc.Invoker, security.Authorizer, string, verror.E) {
- name = strings.TrimLeft(name, "/")
- if method == "Glob" && len(name) == 0 {
- return ipc.ReflectInvoker(&globInvoker{"__debug", fs}), &acceptAllAuthorizer{}, name, nil
+// invoker. Otherwise, and we use the server's dispatcher. The name and method
+// value may be modified to match the actual name and method to use.
+func (fs *flowServer) lookup(name, method *string) (ipc.Invoker, security.Authorizer, verror.E) {
+ *name = strings.TrimLeft(*name, "/")
+ // TODO(rthellend): Remove "Glob" from the condition below after
+ // everything has transitioned to the new name.
+ if *method == "Glob" || *method == ipc.GlobMethod {
+ *method = "Glob"
+ return ipc.ReflectInvoker(&globInternal{fs, *name}), &acceptAllAuthorizer{}, nil
}
- disp := fs.disp
- if strings.HasPrefix(name, naming.ReservedNamePrefix) {
- parts := strings.SplitN(name, "/", 2)
- if len(parts) > 1 {
- name = parts[1]
- } else {
- name = ""
- }
+ var disp ipc.Dispatcher
+ if naming.IsReserved(*name) {
disp = fs.reservedOpt.Dispatcher
+ } else {
+ disp = fs.disp
}
-
if disp != nil {
- invoker, auth, err := lookupInvoker(disp, name, method)
+ invoker, auth, err := lookupInvoker(disp, *name, *method)
switch {
case err != nil:
- return nil, nil, "", verror.Convert(err)
+ return nil, nil, verror.Convert(err)
case invoker != nil:
- return invoker, auth, name, nil
+ return invoker, auth, nil
}
}
- return nil, nil, "", verror.NoExistf("ipc: invoker not found for %q", name)
+ return nil, nil, verror.NoExistf("ipc: invoker not found for %q", *name)
}
type acceptAllAuthorizer struct{}
@@ -937,99 +933,6 @@
return nil
}
-type globInvoker struct {
- prefix string
- fs *flowServer
-}
-
-// Glob matches the pattern against internal object names if the double-
-// underscore prefix is explicitly part of the pattern. Otherwise, it invokes
-// the service's Glob method.
-func (i *globInvoker) Glob(call ipc.ServerCall, pattern string) error {
- g, err := glob.Parse(pattern)
- if err != nil {
- return err
- }
- if strings.HasPrefix(pattern, naming.ReservedNamePrefix) {
- var err error
- // Match against internal object names.
- if ok, _, left := g.MatchInitialSegment(i.prefix); ok {
- if ierr := i.invokeGlob(call, i.fs.reservedOpt.Dispatcher, i.prefix, left.String()); ierr != nil {
- err = ierr
- }
- }
- return err
- }
- // Invoke the service's method.
- return i.invokeGlob(call, i.fs.disp, "", pattern)
-}
-
-func (i *globInvoker) invokeGlob(call ipc.ServerCall, d ipc.Dispatcher, prefix, pattern string) error {
- if d == nil {
- return nil
- }
- obj, auth, err := d.Lookup("", "Glob")
- if err != nil {
- return err
- }
- // TODO(cnicolaou): ipc.Serve TRANSITION
- invoker, ok := obj.(ipc.Invoker)
- if !ok {
- panic(fmt.Errorf("Lookup should have returned an ipc.Invoker, returned %T", obj))
- }
- if obj == nil || !ok {
- return verror.NoExistf("ipc: invoker not found for Glob")
- }
-
- argptrs, tags, err := invoker.Prepare("Glob", 1)
- i.fs.tags = tags
- if err != nil {
- return verror.Makef(verror.ErrorID(err), "%s", err)
- }
- if err := i.fs.authorize(auth); err != nil {
- return err
- }
- leafCall := &localServerCall{call, prefix}
- argptrs[0] = &pattern
- results, err := invoker.Invoke("Glob", leafCall, argptrs)
- if err != nil {
- return err
- }
-
- if len(results) != 1 {
- return verror.BadArgf("unexpected number of results. Got %d, want 1", len(results))
- }
- res := results[0]
- if res == nil {
- return nil
- }
- err, ok = res.(error)
- if !ok {
- return verror.BadArgf("unexpected result type. Got %T, want error", res)
- }
- return err
-}
-
-// An ipc.ServerCall that prepends a prefix to all the names in the streamed
-// MountEntry objects.
-type localServerCall struct {
- ipc.ServerCall
- prefix string
-}
-
-var _ ipc.ServerCall = (*localServerCall)(nil)
-var _ ipc.Stream = (*localServerCall)(nil)
-var _ ipc.ServerContext = (*localServerCall)(nil)
-
-func (c *localServerCall) Send(v interface{}) error {
- me, ok := v.(mttypes.MountEntry)
- if !ok {
- return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
- }
- me.Name = naming.Join(c.prefix, me.Name)
- return c.ServerCall.Send(me)
-}
-
func (fs *flowServer) authorize(auth security.Authorizer) verror.E {
if auth == nil {
auth = defaultAuthorizer{}
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index 9f03323..30a8750 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -4,13 +4,12 @@
"strings"
"time"
- "veyron.io/veyron/veyron/services/mgmt/lib/toplevelglob"
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/security"
+
logreaderimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
pprofimpl "veyron.io/veyron/veyron/services/mgmt/pprof/impl"
statsimpl "veyron.io/veyron/veyron/services/mgmt/stats/impl"
-
- "veyron.io/veyron/veyron2/ipc"
- "veyron.io/veyron/veyron2/security"
)
// dispatcher holds the state of the debug dispatcher.
@@ -25,13 +24,24 @@
return &dispatcher{logsDir, authorizer}
}
+// The first part of the names of the objects served by this dispatcher.
+var rootName = "__debug"
+
func (d *dispatcher) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
+ if suffix == "" {
+ return ipc.VChildrenGlobberInvoker(rootName), d.auth, nil
+ }
+ if !strings.HasPrefix(suffix, rootName) {
+ return nil, nil, nil
+ }
+ suffix = strings.TrimPrefix(suffix, rootName)
+ suffix = strings.TrimLeft(suffix, "/")
+
if method == "Signature" {
return NewSignatureInvoker(suffix), d.auth, nil
}
- if len(suffix) == 0 {
- leaves := []string{"logs", "pprof", "stats"}
- return toplevelglob.New(d, leaves), d.auth, nil
+ if suffix == "" {
+ return ipc.VChildrenGlobberInvoker("logs", "pprof", "stats"), d.auth, nil
}
parts := strings.SplitN(suffix, "/", 2)
if len(parts) == 2 {
@@ -41,7 +51,7 @@
}
switch parts[0] {
case "logs":
- if method == "Glob" {
+ if method == ipc.GlobMethod {
return logreaderimpl.NewLogDirectoryInvoker(d.logsDir, suffix), d.auth, nil
}
return logreaderimpl.NewLogFileInvoker(d.logsDir, suffix), d.auth, nil
diff --git a/services/mgmt/debug/server_test.go b/services/mgmt/debug/dispatcher_test.go
similarity index 81%
rename from services/mgmt/debug/server_test.go
rename to services/mgmt/debug/dispatcher_test.go
index 137181e..4d6f6b9 100644
--- a/services/mgmt/debug/server_test.go
+++ b/services/mgmt/debug/dispatcher_test.go
@@ -1,6 +1,7 @@
-package debug_test
+package debug
import (
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -10,6 +11,8 @@
"testing"
"time"
+ "veyron.io/veyron/veyron2"
+ "veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/services/mgmt/logreader"
@@ -19,11 +22,32 @@
libstats "veyron.io/veyron/veyron/lib/stats"
"veyron.io/veyron/veyron/profiles"
- "veyron.io/veyron/veyron/services/mgmt/debug"
)
+// startDebugServer starts a debug server.
+func startDebugServer(rt veyron2.Runtime, listenSpec ipc.ListenSpec, logsDir string) (string, func(), error) {
+ if len(logsDir) == 0 {
+ return "", nil, fmt.Errorf("logs directory missing")
+ }
+ disp := NewDispatcher(logsDir, nil)
+ server, err := rt.NewServer()
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to start debug server: %v", err)
+ }
+ endpoint, err := server.Listen(listenSpec)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to listen on %s: %v", listenSpec, err)
+ }
+ if err := server.ServeDispatcher("", disp); err != nil {
+ return "", nil, err
+ }
+ ep := endpoint.String()
+ return ep, func() { server.Stop() }, nil
+}
+
func TestDebugServer(t *testing.T) {
runtime := rt.Init()
+ rootName = "debug"
workdir, err := ioutil.TempDir("", "logreadertest")
if err != nil {
@@ -34,7 +58,7 @@
t.Fatalf("ioutil.WriteFile failed: %v", err)
}
- endpoint, stop, err := debug.StartDebugServer(runtime, profiles.LocalListenSpec, workdir, nil)
+ endpoint, stop, err := startDebugServer(runtime, profiles.LocalListenSpec, workdir)
if err != nil {
t.Fatalf("StartDebugServer failed: %v", err)
}
@@ -42,7 +66,7 @@
// Access a logs directory that exists.
{
- ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//logs"))
+ ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "debug/logs"))
stream, err := ld.Glob(runtime.NewContext(), "*")
if err != nil {
t.Errorf("Glob failed: %v", err)
@@ -65,7 +89,7 @@
// Access a logs directory that doesn't exist.
{
- ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//logs/nowheretobefound"))
+ ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "debug/logs/nowheretobefound"))
stream, err := ld.Glob(runtime.NewContext(), "*")
if err != nil {
t.Errorf("Glob failed: %v", err)
@@ -85,7 +109,7 @@
// Access a log file that exists.
{
- lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "//logs/test.INFO"))
+ lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/test.INFO"))
size, err := lf.Size(runtime.NewContext())
if err != nil {
t.Errorf("Size failed: %v", err)
@@ -97,7 +121,7 @@
// Access a log file that doesn't exist.
{
- lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "//logs/nosuchfile.INFO"))
+ lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/nosuchfile.INFO"))
_, err = lf.Size(runtime.NewContext())
if expected := verror.NoExist; !verror.Is(err, expected) {
t.Errorf("unexpected error value, got %v, want: %v", err, expected)
@@ -109,7 +133,7 @@
foo := libstats.NewInteger("testing/foo")
foo.Set(123)
- st := stats.StatsClient(naming.JoinAddressName(endpoint, "//stats/testing/foo"))
+ st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/foo"))
v, err := st.Value(runtime.NewContext())
if err != nil {
t.Errorf("Value failed: %v", err)
@@ -121,7 +145,7 @@
// Access a stats object that doesn't exists.
{
- st := stats.StatsClient(naming.JoinAddressName(endpoint, "//stats/testing/nobodyhome"))
+ st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/nobodyhome"))
_, err = st.Value(runtime.NewContext())
if expected := verror.NoExist; !verror.Is(err, expected) {
t.Errorf("unexpected error value, got %v, want: %v", err, expected)
@@ -131,7 +155,7 @@
// Glob from the root.
{
ns := rt.R().Namespace()
- ns.SetRoots(naming.JoinAddressName(endpoint, "//"))
+ ns.SetRoots(naming.JoinAddressName(endpoint, "debug"))
ctx, cancel := rt.R().NewContext().WithTimeout(10 * time.Second)
defer cancel()
c, err := ns.Glob(ctx, "logs/...")
diff --git a/services/mgmt/debug/server.go b/services/mgmt/debug/server.go
deleted file mode 100644
index 4cff33a..0000000
--- a/services/mgmt/debug/server.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Package debug implements a server that exports debugging information from
-// various sources: log files, stats, etc.
-package debug
-
-import (
- "fmt"
-
- "veyron.io/veyron/veyron2"
- "veyron.io/veyron/veyron2/ipc"
- "veyron.io/veyron/veyron2/security"
- "veyron.io/veyron/veyron2/vlog"
-)
-
-// StartDebugServer starts a debug server.
-func StartDebugServer(rt veyron2.Runtime, listenSpec ipc.ListenSpec, logsDir string, auth security.Authorizer) (string, func(), error) {
- if len(logsDir) == 0 {
- return "", nil, fmt.Errorf("logs directory missing")
- }
- disp := NewDispatcher(logsDir, auth)
- server, err := rt.NewServer()
- if err != nil {
- return "", nil, fmt.Errorf("failed to start debug server: %v", err)
- }
- endpoint, err := server.Listen(listenSpec)
- if err != nil {
- return "", nil, fmt.Errorf("failed to listen on %s: %v", listenSpec, err)
- }
- if err := server.ServeDispatcher("", disp); err != nil {
- return "", nil, err
- }
- ep := endpoint.String()
- vlog.VI(1).Infof("Debug server listening on %v", ep)
- return ep, func() { server.Stop() }, nil
-}
diff --git a/services/mgmt/lib/toplevelglob/invoker.go b/services/mgmt/lib/toplevelglob/invoker.go
deleted file mode 100644
index a73486e..0000000
--- a/services/mgmt/lib/toplevelglob/invoker.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Package toplevelglob implements a Glob invoker that recurses into other
-// invokers.
-package toplevelglob
-
-import (
- "veyron.io/veyron/veyron/lib/glob"
-
- "veyron.io/veyron/veyron2/ipc"
- "veyron.io/veyron/veyron2/naming"
- "veyron.io/veyron/veyron2/services/mounttable/types"
- "veyron.io/veyron/veyron2/verror"
-)
-
-type invoker struct {
- d ipc.Dispatcher
- leaves []string
-}
-
-// New returns a new top-level Glob invoker. The invoker implements a Glob
-// method that will match the given leaves, and recurse into leaf invokers
-// on the given dispatcher.
-func New(d ipc.Dispatcher, leaves []string) ipc.Invoker {
- return ipc.ReflectInvoker(&invoker{d, leaves})
-}
-
-func (i *invoker) Glob(call ipc.ServerCall, pattern string) error {
- g, err := glob.Parse(pattern)
- if err != nil {
- return err
- }
- if g.Len() == 0 {
- call.Send(types.MountEntry{Name: ""})
- }
- for _, leaf := range i.leaves {
- if ok, _, left := g.MatchInitialSegment(leaf); ok {
- if err := i.leafGlob(call, leaf, left.String()); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-func (i *invoker) leafGlob(call ipc.ServerCall, leaf string, pattern string) error {
- obj, _, err := i.d.Lookup(leaf, "Glob")
- if err != nil {
- return err
- }
- // Lookup must return an invoker if it implements its own glob
- invoker, ok := obj.(ipc.Invoker)
- if !ok {
- return nil
- }
- if invoker == nil {
- return verror.BadArgf("failed to find invoker for %q", leaf)
- }
- argptrs := []interface{}{&pattern}
- leafCall := &localServerCall{call, leaf}
- results, err := invoker.Invoke("Glob", leafCall, argptrs)
- if err != nil {
- return err
- }
- if len(results) != 1 {
- return verror.BadArgf("unexpected number of results. Got %d, want 1", len(results))
- }
- res := results[0]
- if res == nil {
- return nil
- }
- err, ok = res.(error)
- if !ok {
- return verror.BadArgf("unexpected result type. Got %T, want error", res)
- }
- return err
-}
-
-// An ipc.ServerCall implementation used to Invoke methods on the invokers
-// directly. Everything is the same as the original ServerCall, except the
-// Stream implementation.
-type localServerCall struct {
- ipc.ServerCall
- name string
-}
-
-// Re-Implement ipc.Stream
-func (c *localServerCall) Recv(v interface{}) error {
- panic("Recv not implemented")
- return nil
-}
-
-func (c *localServerCall) Send(v interface{}) error {
- me, ok := v.(types.MountEntry)
- if !ok {
- return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
- }
- me.Name = naming.Join(c.name, me.Name)
- return c.ServerCall.Send(me)
-}
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index 2b72dcc..f41df15 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -14,7 +14,6 @@
vsecurity "veyron.io/veyron/veyron/security"
vflag "veyron.io/veyron/veyron/security/flag"
"veyron.io/veyron/veyron/security/serialization"
- "veyron.io/veyron/veyron/services/mgmt/lib/toplevelglob"
logsimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
inode "veyron.io/veyron/veyron/services/mgmt/node"
"veyron.io/veyron/veyron/services/mgmt/node/config"
@@ -231,8 +230,8 @@
}
}
if len(components) == 0 {
- if method == "Glob" {
- return toplevelglob.New(d, []string{nodeSuffix, appsSuffix}), d.auth, nil
+ if method == ipc.GlobMethod {
+ return ipc.VChildrenGlobberInvoker(nodeSuffix, appsSuffix), d.auth, nil
}
return nil, nil, errInvalidSuffix
}
@@ -256,7 +255,7 @@
// Requests to apps/*/*/*/pprof are proxied to the apps' __debug/pprof object.
// Requests to apps/*/*/*/stats are proxied to the apps' __debug/stats object.
// Everything else is handled by appInvoker.
- if len(components) >= 5 && (method != "Glob" || components[4] != "logs") {
+ if len(components) >= 5 && (method != ipc.GlobMethod || components[4] != "logs") {
appInstanceDir, err := instanceDir(d.config.Root, components[1:4])
if err != nil {
return nil, nil, err
diff --git a/services/mgmt/node/impl/proxy_invoker.go b/services/mgmt/node/impl/proxy_invoker.go
index fb52ef7..643ad5f 100644
--- a/services/mgmt/node/impl/proxy_invoker.go
+++ b/services/mgmt/node/impl/proxy_invoker.go
@@ -122,8 +122,26 @@
}
func (p *proxyInvoker) VGlob() *ipc.GlobState {
- // TODO(rthellend): Add implementation
- return nil
+ return &ipc.GlobState{VAllGlobber: p}
+}
+
+func (p *proxyInvoker) Glob(call ipc.ServerCall, pattern string) error {
+ argptrs := []interface{}{&pattern}
+ results, err := p.Invoke(ipc.GlobMethod, call, argptrs)
+ if err != nil {
+ return err
+ }
+ if len(results) != 1 {
+ return fmt.Errorf("unexpected number of result values. Got %d, want 1.", len(results))
+ }
+ if results[0] == nil {
+ return nil
+ }
+ err, ok := results[0].(error)
+ if !ok {
+ return fmt.Errorf("unexpected result value type. Got %T, want error.", err)
+ }
+ return err
}
// numResults returns the number of result values for the given method.
@@ -132,6 +150,9 @@
if err != nil {
return 0, err
}
+ if method == ipc.GlobMethod {
+ method = "Glob"
+ }
m, ok := sig.Methods[method]
if !ok {
return 0, fmt.Errorf("unknown method %q", method)