ref: Use the runtime authorizer for vtrace.
Previously I used the authorizer returned by the users dispatcher. The problem
is I'm checking for Debug method tag access and there is no guarantee that
the users authorizer knows or cares about Debug.
The reset of the debug namespace uses the runtime authorizer to authorize debug
requests, so I will follow suit with vtrace.
In the process I also rewrote the vtrace tests substatially.
Change-Id: I825aaeb67be4fe12a4cf47d9d11e468cac4c7293
diff --git a/profiles/internal/rpc/reserved.go b/profiles/internal/rpc/reserved.go
index 890f5c3..7152349 100644
--- a/profiles/internal/rpc/reserved.go
+++ b/profiles/internal/rpc/reserved.go
@@ -345,24 +345,11 @@
}
func callWithSuffix(src rpc.StreamServerCall, suffix string) rpc.StreamServerCall {
- ctx := src.Context()
- secCall := security.GetCall(ctx)
- ctx = security.SetCall(ctx, &derivedSecurityCall{
- Call: secCall,
- suffix: suffix,
- methodTags: secCall.MethodTags(),
- })
- return &derivedServerCall{src, ctx, suffix}
+ return &derivedServerCall{src, securityCallWithSuffix(src.Context(), suffix), suffix}
}
func callWithMethodTags(src rpc.StreamServerCall, tags []*vdl.Value) rpc.StreamServerCall {
- ctx, suffix := src.Context(), src.Suffix()
- ctx = security.SetCall(ctx, &derivedSecurityCall{
- Call: security.GetCall(ctx),
- suffix: suffix,
- methodTags: tags,
- })
- return &derivedServerCall{src, ctx, suffix}
+ return &derivedServerCall{src, securityCallWithMethodTags(src.Context(), tags), src.Suffix()}
}
func (c *derivedServerCall) Context() *context.T { return c.ctx }
@@ -374,5 +361,23 @@
methodTags []*vdl.Value
}
+func securityCallWithSuffix(ctx *context.T, suffix string) *context.T {
+ secCall := security.GetCall(ctx)
+ return security.SetCall(ctx, &derivedSecurityCall{
+ Call: secCall,
+ suffix: suffix,
+ methodTags: secCall.MethodTags(),
+ })
+}
+
+func securityCallWithMethodTags(ctx *context.T, tags []*vdl.Value) *context.T {
+ secCall := security.GetCall(ctx)
+ return security.SetCall(ctx, &derivedSecurityCall{
+ Call: secCall,
+ suffix: secCall.Suffix(),
+ methodTags: tags,
+ })
+}
+
func (c *derivedSecurityCall) Suffix() string { return c.suffix }
func (c *derivedSecurityCall) MethodTags() []*vdl.Value { return c.methodTags }
diff --git a/profiles/internal/rpc/server.go b/profiles/internal/rpc/server.go
index e995815..db689b8 100644
--- a/profiles/internal/rpc/server.go
+++ b/profiles/internal/rpc/server.go
@@ -26,13 +26,12 @@
"v.io/v23/verror"
"v.io/v23/vom"
"v.io/v23/vtrace"
- "v.io/x/lib/vlog"
- "v.io/x/ref/profiles/internal/rpc/stream"
-
"v.io/x/lib/netstate"
+ "v.io/x/lib/vlog"
"v.io/x/ref/lib/stats"
"v.io/x/ref/profiles/internal/lib/publisher"
inaming "v.io/x/ref/profiles/internal/naming"
+ "v.io/x/ref/profiles/internal/rpc/stream"
"v.io/x/ref/profiles/internal/rpc/stream/vc"
// TODO(cnicolaou): finish verror2 -> verror transition, in particular
@@ -157,7 +156,13 @@
var _ rpc.Server = (*server)(nil)
-func InternalNewServer(ctx *context.T, streamMgr stream.Manager, ns namespace.T, client rpc.Client, principal security.Principal, opts ...rpc.ServerOpt) (rpc.Server, error) {
+func InternalNewServer(
+ ctx *context.T,
+ streamMgr stream.Manager,
+ ns namespace.T,
+ client rpc.Client,
+ principal security.Principal,
+ opts ...rpc.ServerOpt) (rpc.Server, error) {
ctx, cancel := context.WithRootCancel(ctx)
ctx, _ = vtrace.SetNewSpan(ctx, "NewServer")
statsPrefix := naming.Join("rpc", "server", "routing-id", streamMgr.RoutingID().String())
@@ -951,7 +956,6 @@
discharges map[string]security.Discharge
starttime time.Time
endStreamArgs bool // are the stream args at EOF?
- allowDebug bool // true if the caller is permitted to view debug information.
}
var _ rpc.Stream = (*flowServer)(nil)
@@ -996,6 +1000,25 @@
return fs, nil
}
+// authorizeVtrace works by simulating a call to __debug/vtrace.Trace. That
+// rpc is essentially equivalent in power to the data we are attempting to
+// attach here.
+func (fs *flowServer) authorizeVtrace() error {
+ // Set up a context as though we were calling __debug/vtrace.
+ params := &security.CallParams{}
+ params.Copy(security.GetCall(fs.T))
+ params.Method = "Trace"
+ params.MethodTags = []*vdl.Value{vdl.ValueOf(access.Debug)}
+ params.Suffix = "__debug/vtrace"
+ ctx := security.SetCall(fs.T, security.NewCall(params))
+
+ var auth security.Authorizer
+ if fs.server.dispReserved != nil {
+ _, auth, _ = fs.server.dispReserved.Lookup(params.Suffix)
+ }
+ return authorize(ctx, auth)
+}
+
func (fs *flowServer) serve() error {
defer fs.flow.Close()
@@ -1004,7 +1027,8 @@
vtrace.GetSpan(fs.T).Finish()
var traceResponse vtrace.Response
- if fs.allowDebug {
+ // Check if the caller is permitted to view vtrace data.
+ if fs.authorizeVtrace() == nil {
traceResponse = vtrace.GetResponse(fs.T)
}
@@ -1122,13 +1146,12 @@
return nil, verror.New(verror.ErrBadProtocol, fs.T, newErrBadInputArg(fs.T, fs.suffix, fs.method, uint64(ix), err))
}
}
+
// Check application's authorization policy.
if err := authorize(fs.T, auth); err != nil {
return nil, err
}
- // Check if the caller is permitted to view debug information.
- // TODO(mattr): Is access.Debug the right thing to check?
- fs.allowDebug = authorize(setDebugCall(fs.T), auth) == nil
+
// Invoke the method.
results, err := invoker.Invoke(strippedMethod, fs, argptrs)
fs.server.stats.record(fs.method, time.Since(fs.starttime))
@@ -1254,20 +1277,6 @@
return nil
}
-// debugSecurityCall wraps another security.Call but always returns
-// the debug method tag.
-type debugSecurityCall struct {
- security.Call
-}
-
-func (debugSecurityCall) MethodTags() []*vdl.Value {
- return []*vdl.Value{vdl.ValueOf(access.Debug)}
-}
-
-func setDebugCall(ctx *context.T) *context.T {
- return security.SetCall(ctx, debugSecurityCall{security.GetCall(ctx)})
-}
-
// Send implements the rpc.Stream method.
func (fs *flowServer) Send(item interface{}) error {
defer vlog.LogCall()()
diff --git a/profiles/internal/rpc/stream/manager/manager.go b/profiles/internal/rpc/stream/manager/manager.go
index c15c6b2..756a479 100644
--- a/profiles/internal/rpc/stream/manager/manager.go
+++ b/profiles/internal/rpc/stream/manager/manager.go
@@ -73,7 +73,7 @@
var _ stream.Manager = (*manager)(nil)
-type DialTimeout struct{ time.Duration }
+type DialTimeout time.Duration
func (DialTimeout) RPCStreamVCOpt() {}
func (DialTimeout) RPCClientOpt() {}
@@ -94,7 +94,7 @@
for _, o := range opts {
switch v := o.(type) {
case DialTimeout:
- timeout = v.Duration
+ timeout = time.Duration(v)
}
}
addr := remote.Addr()
diff --git a/profiles/internal/rpc/stream/manager/manager_test.go b/profiles/internal/rpc/stream/manager/manager_test.go
index e8e6826..b26d57e 100644
--- a/profiles/internal/rpc/stream/manager/manager_test.go
+++ b/profiles/internal/rpc/stream/manager/manager_test.go
@@ -160,7 +160,7 @@
go func() {
// 203.0.113.0 is TEST-NET-3 from RFC5737
ep, _ := inaming.NewEndpoint(naming.FormatEndpoint("tcp", "203.0.113.10:80"))
- _, err := client.Dial(ep, testutil.NewPrincipal("client"), DialTimeout{time.Second})
+ _, err := client.Dial(ep, testutil.NewPrincipal("client"), DialTimeout(time.Second))
ch <- err
}()
diff --git a/profiles/internal/rt/runtime.go b/profiles/internal/rt/runtime.go
index 41fc442..9edd9f5 100644
--- a/profiles/internal/rt/runtime.go
+++ b/profiles/internal/rt/runtime.go
@@ -44,14 +44,19 @@
clientKey
namespaceKey
principalKey
- reservedNameKey
- profileKey
- appCycleKey
- listenSpecKey
- protocolsKey
backgroundKey
+ reservedNameKey
+
+ // initKey is used to store values that are only set at init time.
+ initKey
)
+type initData struct {
+ appCycle v23.AppCycle
+ listenSpec *rpc.ListenSpec
+ protocols []string
+}
+
type vtraceDependency struct{}
// Runtime implements the v23.Runtime interface.
@@ -61,15 +66,25 @@
deps *dependency.Graph
}
-type reservedNameDispatcher struct {
- dispatcher rpc.Dispatcher
- opts []rpc.ServerOpt
-}
-
-func Init(ctx *context.T, appCycle v23.AppCycle, protocols []string, listenSpec *rpc.ListenSpec, flags flags.RuntimeFlags,
- reservedDispatcher rpc.Dispatcher, dispatcherOpts ...rpc.ServerOpt) (*Runtime, *context.T, v23.Shutdown, error) {
+func Init(
+ ctx *context.T,
+ appCycle v23.AppCycle,
+ protocols []string,
+ listenSpec *rpc.ListenSpec,
+ flags flags.RuntimeFlags,
+ reservedDispatcher rpc.Dispatcher) (*Runtime, *context.T, v23.Shutdown, error) {
r := &Runtime{deps: dependency.NewGraph()}
+ ctx = context.WithValue(ctx, initKey, &initData{
+ protocols: protocols,
+ listenSpec: listenSpec,
+ appCycle: appCycle,
+ })
+
+ if reservedDispatcher != nil {
+ ctx = context.WithValue(ctx, reservedNameKey, reservedDispatcher)
+ }
+
err := vlog.ConfigureLibraryLoggerFromFlags()
if err != nil && err != vlog.Configured {
return nil, nil, nil, err
@@ -97,22 +112,6 @@
vtrace.FormatTraces(os.Stderr, vtrace.GetStore(ctx).TraceRecords(), nil)
})
- if reservedDispatcher != nil {
- ctx = context.WithValue(ctx, reservedNameKey, &reservedNameDispatcher{reservedDispatcher, dispatcherOpts})
- }
-
- if appCycle != nil {
- ctx = context.WithValue(ctx, appCycleKey, appCycle)
- }
-
- if len(protocols) > 0 {
- ctx = context.WithValue(ctx, protocolsKey, protocols)
- }
-
- if listenSpec != nil {
- ctx = context.WithValue(ctx, listenSpecKey, listenSpec)
- }
-
// Setup i18n.
ctx = i18n.ContextWithLangID(ctx, i18n.LangIDFromEnv())
if len(flags.I18nCatalogue) != 0 {
@@ -241,16 +240,21 @@
client, _ := ctx.Value(clientKey).(rpc.Client)
otherOpts := append([]rpc.ServerOpt{}, opts...)
- if reserved, ok := ctx.Value(reservedNameKey).(*reservedNameDispatcher); ok {
- otherOpts = append(otherOpts, irpc.ReservedNameDispatcher{reserved.dispatcher})
- otherOpts = append(otherOpts, reserved.opts...)
- }
- if protocols, ok := ctx.Value(protocolsKey).([]string); ok {
- otherOpts = append(otherOpts, irpc.PreferredServerResolveProtocols(protocols))
+
+ if reservedDispatcher := r.GetReservedNameDispatcher(ctx); reservedDispatcher != nil {
+ otherOpts = append(otherOpts, irpc.ReservedNameDispatcher{
+ Dispatcher: reservedDispatcher,
+ })
}
+ id, _ := ctx.Value(initKey).(*initData)
+ if id.protocols != nil {
+ otherOpts = append(otherOpts, irpc.PreferredServerResolveProtocols(id.protocols))
+ }
if !hasServerBlessingsOpt(opts) && principal != nil {
- otherOpts = append(otherOpts, options.ServerBlessings{principal.BlessingStore().Default()})
+ otherOpts = append(otherOpts, options.ServerBlessings{
+ Blessings: principal.BlessingStore().Default(),
+ })
}
server, err := irpc.InternalNewServer(ctx, sm, ns, r.GetClient(ctx), principal, otherOpts...)
if err != nil {
@@ -363,12 +367,11 @@
p, _ := ctx.Value(principalKey).(security.Principal)
sm, _ := ctx.Value(streamManagerKey).(stream.Manager)
ns, _ := ctx.Value(namespaceKey).(namespace.T)
- otherOpts = append(otherOpts, imanager.DialTimeout{5 * time.Minute})
+ otherOpts = append(otherOpts, imanager.DialTimeout(5*time.Minute))
- if protocols, ok := ctx.Value(protocolsKey).([]string); ok {
- otherOpts = append(otherOpts, irpc.PreferredProtocols(protocols))
+ if id, _ := ctx.Value(initKey).(*initData); id.protocols != nil {
+ otherOpts = append(otherOpts, irpc.PreferredProtocols(id.protocols))
}
-
client, err := irpc.InternalNewClient(sm, ns, otherOpts...)
if err != nil {
return ctx, nil, err
@@ -426,13 +429,15 @@
}
func (*Runtime) GetAppCycle(ctx *context.T) v23.AppCycle {
- appCycle, _ := ctx.Value(appCycleKey).(v23.AppCycle)
- return appCycle
+ id, _ := ctx.Value(initKey).(*initData)
+ return id.appCycle
}
func (*Runtime) GetListenSpec(ctx *context.T) rpc.ListenSpec {
- listenSpec, _ := ctx.Value(listenSpecKey).(*rpc.ListenSpec)
- return listenSpec.Copy()
+ if id, _ := ctx.Value(initKey).(*initData); id.listenSpec != nil {
+ return id.listenSpec.Copy()
+ }
+ return rpc.ListenSpec{}
}
func (*Runtime) SetBackgroundContext(ctx *context.T) *context.T {
@@ -456,17 +461,12 @@
}
func (*Runtime) SetReservedNameDispatcher(ctx *context.T, d rpc.Dispatcher) *context.T {
- rnd := &reservedNameDispatcher{dispatcher: d}
- if oldRnd, ok := ctx.Value(reservedNameKey).(*reservedNameDispatcher); ok {
- rnd.opts = oldRnd.opts
- }
- newctx := context.WithValue(ctx, reservedNameKey, rnd)
- return newctx
+ return context.WithValue(ctx, reservedNameKey, d)
}
func (*Runtime) GetReservedNameDispatcher(ctx *context.T) rpc.Dispatcher {
- if d, ok := ctx.Value(reservedNameKey).(*reservedNameDispatcher); ok {
- return d.dispatcher
+ if d, ok := ctx.Value(reservedNameKey).(rpc.Dispatcher); ok {
+ return d
}
return nil
}
diff --git a/profiles/internal/vtrace/v23_internal_test.go b/profiles/internal/vtrace/v23_internal_test.go
deleted file mode 100644
index c454bfe..0000000
--- a/profiles/internal/vtrace/v23_internal_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-package vtrace
-
-import "testing"
-import "os"
-
-import "v.io/x/ref/test"
-
-func TestMain(m *testing.M) {
- test.Init()
- os.Exit(m.Run())
-}
diff --git a/profiles/internal/vtrace/vtrace_test.go b/profiles/internal/vtrace/vtrace_test.go
index 63090f4..a1a73f6 100644
--- a/profiles/internal/vtrace/vtrace_test.go
+++ b/profiles/internal/vtrace/vtrace_test.go
@@ -6,31 +6,62 @@
import (
"bytes"
+ "fmt"
"strings"
"testing"
"v.io/v23"
"v.io/v23/context"
- "v.io/v23/namespace"
- "v.io/v23/naming"
+ "v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
+ "v.io/v23/security/access"
+ "v.io/v23/uniqueid"
"v.io/v23/vtrace"
- "v.io/x/lib/vlog"
+ "v.io/x/ref/lib/flags"
+ _ "v.io/x/ref/lib/security/securityflag"
_ "v.io/x/ref/profiles"
- irpc "v.io/x/ref/profiles/internal/rpc"
- "v.io/x/ref/profiles/internal/rpc/stream"
- "v.io/x/ref/profiles/internal/rpc/stream/manager"
- tnaming "v.io/x/ref/profiles/internal/testing/mocks/naming"
+ ivtrace "v.io/x/ref/profiles/internal/vtrace"
+ "v.io/x/ref/services/mounttable/mounttablelib"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
-//go:generate v23 test generate
+func init() {
+ test.Init()
+}
+
+// initForTest initializes the vtrace runtime and starts a mounttable.
+func initForTest(t *testing.T) (*context.T, v23.Shutdown, *testutil.IDProvider) {
+ idp := testutil.NewIDProvider("base")
+ ctx, shutdown := test.InitForTest()
+ if err := idp.Bless(v23.GetPrincipal(ctx), "alice"); err != nil {
+ t.Fatalf("Could not bless initial principal %v", err)
+ }
+
+ // Start a local mounttable.
+ s, err := v23.NewServer(ctx, options.ServesMountTable(true))
+ if err != nil {
+ t.Fatalf("Could not create mt server %v", err)
+ }
+ eps, err := s.Listen(v23.GetListenSpec(ctx))
+ if err != nil {
+ t.Fatalf("Could not listen for mt %v", err)
+ }
+ disp, err := mounttablelib.NewMountTableDispatcher("")
+ if err != nil {
+ t.Fatalf("Could not create mt dispatcher %v", err)
+ }
+ if err := s.ServeDispatcher("", disp); err != nil {
+ t.Fatalf("Could not serve mt dispatcher %v", err)
+ }
+ v23.GetNamespace(ctx).SetRoots(eps[0].Name())
+ return ctx, shutdown, idp
+}
func TestNewFromContext(t *testing.T) {
- c0, shutdown := test.InitForTest()
+ c0, shutdown, _ := initForTest(t)
defer shutdown()
c1, s1 := vtrace.SetNewSpan(c0, "s1")
c2, s2 := vtrace.SetNewSpan(c1, "s2")
@@ -47,15 +78,9 @@
}
}
-type fakeAuthorizer int
-
-func (fakeAuthorizer) Authorize(*context.T) error {
- return nil
-}
-
+// testServer can be easily configured to have child servers of the
+// same type which it will call when it receives a call.
type testServer struct {
- sm stream.Manager
- ns namespace.T
name string
child string
stop func() error
@@ -63,62 +88,102 @@
}
func (c *testServer) Run(call rpc.ServerCall) error {
+ ctx := call.Context()
if c.forceCollect {
- vtrace.ForceCollect(call.Context())
+ vtrace.ForceCollect(ctx)
}
-
- client, err := irpc.InternalNewClient(c.sm, c.ns)
- if err != nil {
- vlog.Error(err)
- return err
- }
-
- vtrace.GetSpan(call.Context()).Annotate(c.name + "-begin")
-
+ vtrace.GetSpan(ctx).Annotate(c.name + "-begin")
if c.child != "" {
- var clientCall rpc.ClientCall
- if clientCall, err = client.StartCall(call.Context(), c.child, "Run", []interface{}{}); err != nil {
- vlog.Error(err)
+ clientCall, err := v23.GetClient(ctx).StartCall(ctx, c.child, "Run", nil)
+ if err != nil {
return err
}
if err := clientCall.Finish(); err != nil {
- vlog.Error(err)
return err
}
}
- vtrace.GetSpan(call.Context()).Annotate(c.name + "-end")
-
+ vtrace.GetSpan(ctx).Annotate(c.name + "-end")
return nil
}
-func makeTestServer(ctx *context.T, principal security.Principal, ns namespace.T, name, child string, forceCollect bool) (*testServer, error) {
- sm := manager.InternalNew(naming.FixedRoutingID(0x111111111))
- client, err := irpc.InternalNewClient(sm, ns)
+func runCallChain(t *testing.T, ctx *context.T, idp *testutil.IDProvider, force1, force2 bool) *vtrace.TraceRecord {
+ ctx, span := vtrace.SetNewSpan(ctx, "")
+ span.Annotate("c0-begin")
+ _, stop, err := makeChainedTestServers(ctx, idp, force1, force2)
if err != nil {
- return nil, err
+ t.Fatalf("Could not start servers %v", err)
}
- s, err := irpc.InternalNewServer(ctx, sm, ns, client, principal)
+ defer stop()
+ call, err := v23.GetClient(ctx).StartCall(ctx, "c1", "Run", nil)
if err != nil {
- return nil, err
+ t.Fatal("can't call: ", err)
}
+ if err := call.Finish(); err != nil {
+ t.Error(err)
+ }
+ span.Annotate("c0-end")
+ span.Finish()
+ return vtrace.GetStore(ctx).TraceRecord(span.Trace())
+}
+
+func makeChainedTestServers(ctx *context.T, idp *testutil.IDProvider, force ...bool) ([]*testServer, func(), error) {
+ out := []*testServer{}
+ last := len(force) - 1
+ ext := "alice"
+ for i, f := range force {
+ name := fmt.Sprintf("c%d", i+1)
+ ext += "/" + name
+ principal := testutil.NewPrincipal()
+ if err := idp.Bless(principal, ext); err != nil {
+ return nil, nil, err
+ }
+ c, err := makeTestServer(ctx, principal, name)
+ if err != nil {
+ return nil, nil, err
+ }
+ if i < last {
+ c.child = fmt.Sprintf("c%d", i+2)
+ }
+ c.forceCollect = f
+ out = append(out, c)
+ }
+ return out, func() {
+ for _, s := range out {
+ s.stop()
+ }
+ }, nil
+}
+
+type anyone struct{}
+
+func (anyone) Authorize(ctx *context.T) error { return nil }
+
+func makeTestServer(ctx *context.T, principal security.Principal, name string) (*testServer, error) {
+ // Set a new vtrace store to simulate a separate process.
+ ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{CacheSize: 100})
+ if err != nil {
+ return nil, err
+ }
+ ctx, _ = vtrace.SetNewTrace(ctx)
+ ctx, err = v23.SetPrincipal(ctx, principal)
+ if err != nil {
+ return nil, err
+ }
+ s, err := v23.NewServer(ctx)
+ if err != nil {
+ return nil, err
+ }
if _, err := s.Listen(v23.GetListenSpec(ctx)); err != nil {
return nil, err
}
-
c := &testServer{
- sm: sm,
- ns: ns,
- name: name,
- child: child,
- stop: s.Stop,
- forceCollect: forceCollect,
+ name: name,
+ stop: s.Stop,
}
-
- if err := s.Serve(name, c, fakeAuthorizer(0)); err != nil {
+ if err := s.Serve(name, c, anyone{}); err != nil {
return nil, err
}
-
return c, nil
}
@@ -140,148 +205,172 @@
return b.String()
}
-func expectSequence(t *testing.T, trace vtrace.TraceRecord, expectedSpans []string) {
- // It's okay to have additional spans - someone may have inserted
- // additional spans for more debugging.
- if got, want := len(trace.Spans), len(expectedSpans); got < want {
- t.Errorf("Found %d spans, want %d", got, want)
- }
+type spanSet map[uniqueid.Id]*vtrace.SpanRecord
- spans := map[string]*vtrace.SpanRecord{}
- summaries := []string{}
+func newSpanSet(trace vtrace.TraceRecord) spanSet {
+ out := spanSet{}
for i := range trace.Spans {
span := &trace.Spans[i]
+ out[span.Id] = span
+ }
+ return out
+}
- // All spans should have a start.
- if span.Start.IsZero() {
- t.Errorf("span missing start: %x, %s", span.Id[12:], traceString(&trace))
+func (s spanSet) hasAncestor(span *vtrace.SpanRecord, ancestor *vtrace.SpanRecord) bool {
+ for span = s[span.Parent]; span != nil; span = s[span.Parent] {
+ if span == ancestor {
+ return true
}
- // All spans except the root should have a valid end.
- // TODO(mattr): For now I'm also skipping connectFlow and
- // vc.HandshakeDialedVC spans because the ws endpoints are
- // currently non-deterministic in terms of whether they fail
- // before the test ends or not. In the future it will be
- // configurable whether we listen on ws or not and then we should
- // adjust the test to not listen and remove this check.
- if span.Name != "" &&
- span.Name != "<client>connectFlow" &&
- span.Name != "vc.HandshakeDialedVC" {
- if span.End.IsZero() {
- t.Errorf("span missing end: %x, %s", span.Id[12:], traceString(&trace))
- } else if !span.Start.Before(span.End) {
- t.Errorf("span end should be after start: %x, %s", span.Id[12:], traceString(&trace))
- }
- }
+ }
+ return false
+}
- summary := summary(span)
- summaries = append(summaries, summary)
- spans[summary] = span
+func expectSequence(t *testing.T, trace vtrace.TraceRecord, expectedSpans []string) {
+ s := newSpanSet(trace)
+ found := make(map[string]*vtrace.SpanRecord)
+ for _, es := range expectedSpans {
+ found[es] = nil
}
- for i := range expectedSpans {
- child, ok := spans[expectedSpans[i]]
- if !ok {
- t.Errorf("expected span %s not found in %#v", expectedSpans[i], summaries)
+ for i := range trace.Spans {
+ span := &trace.Spans[i]
+ smry := summary(span)
+ if _, ok := found[smry]; ok {
+ found[smry] = span
+ }
+ }
+
+ for i, es := range expectedSpans {
+ span := found[es]
+ if span == nil {
+ t.Errorf("expected span %s not found in\n%s", es, traceString(&trace))
continue
}
+ // All spans should have a start.
+ if span.Start.IsZero() {
+ t.Errorf("span missing start: %x\n%s", span.Id[12:], traceString(&trace))
+ }
+ // All spans except the root should have a valid end.
+ if span.Parent != trace.Id {
+ if span.End.IsZero() {
+ t.Errorf("span missing end: %x\n%s", span.Id[12:], traceString(&trace))
+ } else if !span.Start.Before(span.End) {
+ t.Errorf("span end should be after start: %x\n%s", span.Id[12:], traceString(&trace))
+ }
+ }
+ // Spans should decend from the previous span in the list.
if i == 0 {
continue
}
- parent, ok := spans[expectedSpans[i-1]]
- if !ok {
- t.Errorf("expected span %s not found in %#v", expectedSpans[i-1], summaries)
- continue
+ if ancestor := found[expectedSpans[i-1]]; ancestor != nil && !s.hasAncestor(span, ancestor) {
+ t.Errorf("span %s does not have ancestor %s", es, expectedSpans[i-1])
}
- if child.Parent != parent.Id {
- t.Errorf("%v should be a child of %v, but it's not.", child, parent)
- }
- }
-}
-
-func runCallChain(t *testing.T, ctx *context.T, force1, force2 bool) {
- var (
- sm = manager.InternalNew(naming.FixedRoutingID(0x555555555))
- ns = tnaming.NewSimpleNamespace()
- pclient = testutil.NewPrincipal("client")
- pserver1 = testutil.NewPrincipal("server1")
- pserver2 = testutil.NewPrincipal("server2")
- )
- for _, p1 := range []security.Principal{pclient, pserver1, pserver2} {
- for _, p2 := range []security.Principal{pclient, pserver1, pserver2} {
- p1.AddToRoots(p2.BlessingStore().Default())
- }
- }
- ctx, _ = v23.SetPrincipal(ctx, pclient)
- client, err := irpc.InternalNewClient(sm, ns)
- if err != nil {
- t.Error(err)
- }
- ctx1, _ := vtrace.SetNewTrace(ctx)
- c1, err := makeTestServer(ctx1, pserver1, ns, "c1", "c2", force1)
- if err != nil {
- t.Fatal("Can't start server:", err)
- }
- defer c1.stop()
-
- ctx2, _ := vtrace.SetNewTrace(ctx)
- c2, err := makeTestServer(ctx2, pserver2, ns, "c2", "", force2)
- if err != nil {
- t.Fatal("Can't start server:", err)
- }
- defer c2.stop()
-
- call, err := client.StartCall(ctx, "c1", "Run", []interface{}{})
- if err != nil {
- t.Fatal("can't call: ", err)
- }
- if err := call.Finish(); err != nil {
- t.Error(err)
}
}
// TestCancellationPropagation tests that cancellation propogates along an
// RPC call chain without user intervention.
func TestTraceAcrossRPCs(t *testing.T) {
- ctx, shutdown := test.InitForTest()
+ ctx, shutdown, idp := initForTest(t)
defer shutdown()
- ctx, span := vtrace.SetNewSpan(ctx, "")
+
vtrace.ForceCollect(ctx)
- span.Annotate("c0-begin")
+ record := runCallChain(t, ctx, idp, false, false)
- runCallChain(t, ctx, false, false)
-
- span.Annotate("c0-end")
-
- expectedSpans := []string{
+ expectSequence(t, *record, []string{
": c0-begin, c0-end",
"<rpc.Client>\"c1\".Run",
"\"\".Run: c1-begin, c1-end",
"<rpc.Client>\"c2\".Run",
"\"\".Run: c2-begin, c2-end",
- }
- record := vtrace.GetStore(ctx).TraceRecord(span.Trace())
- expectSequence(t, *record, expectedSpans)
+ })
}
// TestCancellationPropagationLateForce tests that cancellation propogates along an
// RPC call chain when tracing is initiated by someone deep in the call chain.
func TestTraceAcrossRPCsLateForce(t *testing.T) {
- ctx, shutdown := test.InitForTest()
+ ctx, shutdown, idp := initForTest(t)
defer shutdown()
- ctx, span := vtrace.SetNewSpan(ctx, "")
- span.Annotate("c0-begin")
- runCallChain(t, ctx, false, true)
+ record := runCallChain(t, ctx, idp, false, true)
- span.Annotate("c0-end")
-
- expectedSpans := []string{
+ expectSequence(t, *record, []string{
": c0-end",
"<rpc.Client>\"c1\".Run",
"\"\".Run: c1-end",
"<rpc.Client>\"c2\".Run",
"\"\".Run: c2-begin, c2-end",
+ })
+}
+
+func traceWithAuth(t *testing.T, ctx *context.T, principal security.Principal) bool {
+ s, err := makeTestServer(ctx, principal, "server")
+ if err != nil {
+ t.Fatalf("Couldn't start server %v", err)
+ }
+ defer s.stop()
+
+ ctx, span := vtrace.SetNewTrace(ctx)
+ vtrace.ForceCollect(ctx)
+
+ ctx, client, err := v23.SetNewClient(ctx)
+ if err != nil {
+ t.Fatalf("Couldn't create client %v", err)
+ }
+ call, err := client.StartCall(ctx, "server", "Run", nil)
+ if err != nil {
+ t.Fatalf("Couldn't make call %v", err)
+ }
+ if err = call.Finish(); err != nil {
+ t.Fatalf("Couldn't complete call %v", err)
}
record := vtrace.GetStore(ctx).TraceRecord(span.Trace())
- expectSequence(t, *record, expectedSpans)
+ for _, sp := range record.Spans {
+ if sp.Name == `"".Run` {
+ return true
+ }
+ }
+ return false
+}
+
+type debugDispatcher string
+
+func (acls debugDispatcher) Lookup(string) (interface{}, security.Authorizer, error) {
+ perms, err := access.ReadPermissions(strings.NewReader(string(acls)))
+ if err != nil {
+ return nil, nil, err
+ }
+ auth, err := access.PermissionsAuthorizer(perms, access.TypicalTagType())
+ if err != nil {
+ return nil, nil, err
+ }
+ return nil, auth, nil
+}
+
+// TestPermissions tests that only permitted users are allowed to gather tracing
+// information.
+func TestTracePermissions(t *testing.T) {
+ ctx, shutdown, idp := initForTest(t)
+ defer shutdown()
+
+ type testcase struct {
+ perms string
+ spans bool
+ }
+ cases := []testcase{
+ {`{}`, false},
+ {`{"Read":{"In": ["base/alice"]}, "Write":{"In": ["base/alice"]}}`, false},
+ {`{"Debug":{"In": ["base/alice"]}}`, true},
+ }
+
+ // Create a different principal for the server.
+ pserver := testutil.NewPrincipal()
+ idp.Bless(pserver, "server")
+
+ for _, tc := range cases {
+ ctx2 := v23.SetReservedNameDispatcher(ctx, debugDispatcher(tc.perms))
+ if found := traceWithAuth(t, ctx2, pserver); found != tc.spans {
+ t.Errorf("got %v wanted %v for perms %s", found, tc.spans, tc.perms)
+ }
+ }
}