Merge "More "go vet" fixes."
diff --git a/lib/vdl/codegen/java/file_client_interface.go b/lib/vdl/codegen/java/file_client_interface.go
index db4366f..ffc17c8 100644
--- a/lib/vdl/codegen/java/file_client_interface.go
+++ b/lib/vdl/codegen/java/file_client_interface.go
@@ -23,6 +23,7 @@
{{ range $method := .Methods }}
{{/* If this method has multiple return arguments, generate the class. */}}
{{ if $method.IsMultipleRet }}
+ @io.v.v23.vdl.MultiReturn
public static class {{ $method.UppercaseMethodName }}Out {
{{ range $retArg := $method.RetArgs }}
public {{ $retArg.Type }} {{ $retArg.Name }};
@@ -47,8 +48,8 @@
AccessModifier string
Args string
Doc string
- IsMultipleRet bool
Name string
+ IsMultipleRet bool
RetArgs []clientInterfaceArg
RetType string
UppercaseMethodName string
@@ -66,8 +67,8 @@
}
}
-func clientInterfaceOutArg(iface *compile.Interface, method *compile.Method, isService bool, env *compile.Env) string {
- if isStreamingMethod(method) && !isService {
+func clientInterfaceOutArg(iface *compile.Interface, method *compile.Method, env *compile.Env) string {
+ if isStreamingMethod(method) {
return fmt.Sprintf("io.v.v23.vdl.ClientStream<%s, %s, %s>", javaType(method.InStream, true, env), javaType(method.OutStream, true, env), clientInterfaceNonStreamingOutArg(iface, method, true, env))
}
return clientInterfaceNonStreamingOutArg(iface, method, false, env)
@@ -87,10 +88,10 @@
AccessModifier: accessModifierForName(method.Name),
Args: javaDeclarationArgStr(method.InArgs, env, true),
Doc: method.Doc,
- IsMultipleRet: len(retArgs) > 1,
Name: vdlutil.FirstRuneToLower(method.Name),
+ IsMultipleRet: len(retArgs) > 1,
RetArgs: retArgs,
- RetType: clientInterfaceOutArg(iface, method, false, env),
+ RetType: clientInterfaceOutArg(iface, method, env),
UppercaseMethodName: method.Name,
}
}
diff --git a/lib/vdl/codegen/java/file_client_stub.go b/lib/vdl/codegen/java/file_client_stub.go
index 4f8c3dc..fd28e07 100644
--- a/lib/vdl/codegen/java/file_client_stub.go
+++ b/lib/vdl/codegen/java/file_client_stub.go
@@ -43,24 +43,6 @@
}
- // Methods from interface UniversalServiceMethods.
- @Override
- public io.v.v23.vdlroot.signature.Interface getSignature(io.v.v23.context.VContext context) throws io.v.v23.verror.VException {
- return getSignature(context, null);
- }
- @Override
- public io.v.v23.vdlroot.signature.Interface getSignature(io.v.v23.context.VContext context, io.v.v23.Options vOpts) throws io.v.v23.verror.VException {
- // Start the call.
- final io.v.v23.rpc.Client.Call _call = getClient(context).startCall(context, this.vName, "signature", new java.lang.Object[0], new java.lang.reflect.Type[0], vOpts);
-
- // Finish the call.
- final java.lang.reflect.Type[] _resultTypes = new java.lang.reflect.Type[]{
- new com.google.common.reflect.TypeToken<io.v.v23.vdlroot.signature.Interface>() {}.getType(),
- };
- final java.lang.Object[] _results = _call.finish(_resultTypes);
- return (io.v.v23.vdlroot.signature.Interface)_results[0];
- }
-
// Methods from interface {{ .ServiceName }}Client.
{{/* Iterate over methods defined directly in the body of this service */}}
{{ range $method := .Methods }}
@@ -218,7 +200,7 @@
NotStreaming: !isStreamingMethod(method),
OutArgs: outArgs,
RecvType: javaType(method.OutStream, true, env),
- RetType: clientInterfaceOutArg(iface, method, false, env),
+ RetType: clientInterfaceOutArg(iface, method, env),
Returns: len(method.OutArgs) >= 1 || isStreamingMethod(method),
SendType: javaType(method.InStream, true, env),
ServiceName: vdlutil.FirstRuneToUpper(iface.Name),
@@ -232,7 +214,7 @@
DeclarationArgs: javaDeclarationArgStr(embedMethod.InArgs, env, true),
LocalStubVarName: vdlutil.FirstRuneToLower(iface.Name) + "ClientStub",
Name: vdlutil.FirstRuneToLower(embedMethod.Name),
- RetType: clientInterfaceOutArg(iface, embedMethod, false, env),
+ RetType: clientInterfaceOutArg(iface, embedMethod, env),
Returns: len(embedMethod.OutArgs) >= 1 || isStreamingMethod(embedMethod),
}
}
diff --git a/lib/vdl/codegen/java/file_server_interface.go b/lib/vdl/codegen/java/file_server_interface.go
index 947fa66..d3d8891 100644
--- a/lib/vdl/codegen/java/file_server_interface.go
+++ b/lib/vdl/codegen/java/file_server_interface.go
@@ -24,6 +24,16 @@
)
{{ .AccessModifier }} interface {{ .ServiceName }}Server {{ .Extends }} {
{{ range $method := .Methods }}
+ {{/* If this method has multiple return arguments, generate the class. */}}
+ {{ if $method.IsMultipleRet }}
+ @io.v.v23.vdl.MultiReturn
+ public static class {{ $method.UppercaseMethodName }}Out {
+ {{ range $retArg := $method.RetArgs }}
+ public {{ $retArg.Type }} {{ $retArg.Name }};
+ {{ end }}
+ }
+ {{ end }}
+
{{/* Generate the method signature. */}}
{{ $method.Doc }}
{{ $method.AccessModifier }} {{ $method.RetType }} {{ $method.Name }}(final io.v.v23.context.VContext ctx, final io.v.v23.rpc.ServerCall call{{ $method.Args }}) throws io.v.v23.verror.VException;
@@ -31,23 +41,31 @@
}
`
-func serverInterfaceOutArg(method *compile.Method, iface *compile.Interface, env *compile.Env) string {
+func serverInterfaceOutArg(iface *compile.Interface, method *compile.Method, env *compile.Env) string {
switch len(method.OutArgs) {
case 0:
return "void"
case 1:
return javaType(method.OutArgs[0].Type, false, env)
default:
- return javaPath(path.Join(interfaceFullyQualifiedName(iface)+"Client", method.Name+"Out"))
+ return javaPath(path.Join(interfaceFullyQualifiedName(iface)+"Server", method.Name+"Out"))
}
}
+type serverInterfaceArg struct {
+ Type string
+ Name string
+}
+
type serverInterfaceMethod struct {
- AccessModifier string
- Args string
- Doc string
- Name string
- RetType string
+ AccessModifier string
+ Args string
+ Doc string
+ Name string
+ IsMultipleRet bool
+ RetArgs []serverInterfaceArg
+ RetType string
+ UppercaseMethodName string
}
func processServerInterfaceMethod(method *compile.Method, iface *compile.Interface, env *compile.Env) serverInterfaceMethod {
@@ -55,12 +73,25 @@
if isStreamingMethod(method) {
args += fmt.Sprintf(", io.v.v23.vdl.Stream<%s, %s> stream", javaType(method.OutStream, true, env), javaType(method.InStream, true, env))
}
+ retArgs := make([]serverInterfaceArg, len(method.OutArgs))
+ for i := 0; i < len(method.OutArgs); i++ {
+ if method.OutArgs[i].Name != "" {
+ retArgs[i].Name = vdlutil.FirstRuneToLower(method.OutArgs[i].Name)
+ } else {
+ retArgs[i].Name = fmt.Sprintf("ret%d", i+1)
+ }
+ retArgs[i].Type = javaType(method.OutArgs[i].Type, false, env)
+ }
+
return serverInterfaceMethod{
- AccessModifier: accessModifierForName(method.Name),
- Args: args,
- Doc: method.Doc,
- Name: vdlutil.FirstRuneToLower(method.Name),
- RetType: serverInterfaceOutArg(method, iface, env),
+ AccessModifier: accessModifierForName(method.Name),
+ Args: args,
+ Doc: method.Doc,
+ Name: vdlutil.FirstRuneToLower(method.Name),
+ IsMultipleRet: len(retArgs) > 1,
+ RetArgs: retArgs,
+ RetType: serverInterfaceOutArg(iface, method, env),
+ UppercaseMethodName: method.Name,
}
}
diff --git a/lib/vdl/codegen/java/file_server_wrapper.go b/lib/vdl/codegen/java/file_server_wrapper.go
index d7b69f9..5043d6d 100644
--- a/lib/vdl/codegen/java/file_server_wrapper.go
+++ b/lib/vdl/codegen/java/file_server_wrapper.go
@@ -39,7 +39,7 @@
/**
* Returns a description of this server.
*/
- public io.v.v23.vdlroot.signature.Interface signature(final io.v.v23.context.VContext ctx, final io.v.v23.rpc.ServerCall call) throws io.v.v23.verror.VException {
+ public io.v.v23.vdlroot.signature.Interface signature() {
java.util.List<io.v.v23.vdlroot.signature.Embed> embeds = new java.util.ArrayList<io.v.v23.vdlroot.signature.Embed>();
java.util.List<io.v.v23.vdlroot.signature.Method> methods = new java.util.ArrayList<io.v.v23.vdlroot.signature.Method>();
{{ range $method := .Methods }}
@@ -75,7 +75,7 @@
* by this server.
*/
@SuppressWarnings("unused")
- public io.v.v23.vdl.VdlValue[] getMethodTags(final io.v.v23.rpc.StreamServerCall call, final java.lang.String method) throws io.v.v23.verror.VException {
+ public io.v.v23.vdl.VdlValue[] getMethodTags(final java.lang.String method) throws io.v.v23.verror.VException {
{{ range $methodName, $tags := .MethodTags }}
if ("{{ $methodName }}".equals(method)) {
try {
@@ -89,7 +89,7 @@
{{ end }}
{{ range $embed := .Embeds }}
{
- final io.v.v23.vdl.VdlValue[] tags = this.{{ $embed.LocalWrapperVarName }}.getMethodTags(call, method);
+ final io.v.v23.vdl.VdlValue[] tags = this.{{ $embed.LocalWrapperVarName }}.getMethodTags(method);
if (tags != null) {
return tags;
}
@@ -195,7 +195,7 @@
IsStreaming: isStreamingMethod(method),
Name: vdlutil.FirstRuneToLower(method.Name),
RecvType: javaType(method.InStream, true, env),
- RetType: clientInterfaceOutArg(iface, method, true, env),
+ RetType: serverInterfaceOutArg(iface, method, env),
RetJavaTypes: retArgTypes,
Returns: len(method.OutArgs) >= 1,
SendType: javaType(method.OutStream, true, env),
@@ -211,7 +211,7 @@
DeclarationArgs: javaDeclarationArgStr(embedMethod.InArgs, env, true),
LocalWrapperVarName: vdlutil.FirstRuneToLower(iface.Name) + "Wrapper",
Name: vdlutil.FirstRuneToLower(embedMethod.Name),
- RetType: clientInterfaceOutArg(iface, embedMethod, true, env),
+ RetType: serverInterfaceOutArg(iface, embedMethod, env),
Returns: len(embedMethod.OutArgs) >= 1,
}
}
@@ -227,9 +227,6 @@
})
}
methodTags := make(map[string][]methodTag)
- // Add generated methods to the tag map:
- methodTags["signature"] = []methodTag{}
- methodTags["getMethodTags"] = []methodTag{}
// Copy method tags off of the interface.
methods := make([]serverWrapperMethod, len(iface.Methods))
for i, method := range iface.Methods {
diff --git a/lib/vdl/codegen/java/util_interface.go b/lib/vdl/codegen/java/util_interface.go
index 73bc317..9a35327 100644
--- a/lib/vdl/codegen/java/util_interface.go
+++ b/lib/vdl/codegen/java/util_interface.go
@@ -38,14 +38,18 @@
// javaClientExtendsStr creates an extends clause for a client interface
// e.g. "extends com.a.B, com.d.E"
func javaClientExtendsStr(embeds []*compile.Interface) string {
+ if len(embeds) == 0 {
+ return ""
+ }
var buf bytes.Buffer
buf.WriteString("extends ")
- for _, embed := range embeds {
+ for i, embed := range embeds {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
buf.WriteString(javaPath(interfaceFullyQualifiedName(embed)))
buf.WriteString("Client")
- buf.WriteString(", ")
}
- buf.WriteString("io.v.v23.rpc.UniversalServiceMethods")
return buf.String()
}
diff --git a/profiles/internal/naming/namespace/all_test.go b/profiles/internal/naming/namespace/all_test.go
index 236fb6a..07f540a 100644
--- a/profiles/internal/naming/namespace/all_test.go
+++ b/profiles/internal/naming/namespace/all_test.go
@@ -61,7 +61,7 @@
// N squared but who cares, this is a little test.
// Ignores dups.
-func contains(container, contained []string) bool {
+func contains(container, contained []string) (string, bool) {
L:
for _, d := range contained {
for _, r := range container {
@@ -69,21 +69,31 @@
continue L
}
}
- return false
+ return d, false
}
- return true
+ return "", true
}
func compare(t *testing.T, caller, name string, got, want []string) {
// Compare ignoring dups.
- if !contains(got, want) || !contains(want, got) {
- boom(t, "%s: %q: got %v, want %v", caller, name, got, want)
+ a, foundA := contains(got, want)
+ b, foundB := contains(want, got)
+ if !foundA {
+ vlog.Infof("%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+ boom(t, "%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+ }
+ if !foundB {
+ vlog.Infof("%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+ boom(t, "%s: %q: failed to find %q: got %v, want %v", caller, name, b, got, want)
}
}
func doGlob(t *testing.T, ctx *context.T, ns namespace.T, pattern string, limit int) []string {
var replies []string
- rc, err := ns.Glob(ctx, pattern)
+
+ sctx, done := context.WithTimeout(ctx, 2*time.Minute)
+ defer done()
+ rc, err := ns.Glob(sctx, pattern)
if err != nil {
boom(t, "Glob(%s): %s", pattern, err)
}
@@ -94,6 +104,8 @@
if limit > 0 && len(replies) > limit {
boom(t, "Glob returns too many results, perhaps not limiting recursion")
}
+ case *naming.GlobReplyError:
+ boom(t, "Glob failed at %q: %v", v.Value.Name, v.Value.Error)
}
}
return replies
@@ -141,11 +153,23 @@
}
func doResolveTest(t *testing.T, fname string, f func(*context.T, string, ...naming.NamespaceOpt) (*naming.MountEntry, error), ctx *context.T, name string, want []string, opts ...naming.NamespaceOpt) {
- me, err := f(ctx, name, opts...)
- if err != nil {
- boom(t, "Failed to %s %s: %s", fname, name, err)
+ maxretries := 5
+ var lastErr error
+ for i := 0; i < maxretries; i++ {
+ me, err := f(ctx, name, opts...)
+ if err == nil {
+ if i > 0 {
+ t.Logf("doResolveTest: retried %d times", i)
+ }
+ compare(t, fname, name, me.Names(), want)
+ return
+ }
+ if err != nil && verror.Action(err).RetryAction() != 0 {
+ boom(t, "Failed to %s %s: %s, attempt %d", fname, name, err, i)
+ }
+ lastErr = err
}
- compare(t, fname, name, me.Names(), want)
+ boom(t, "Failed to %s %s: %s after %d attempts", fname, name, lastErr, maxretries)
}
func testResolveToMountTable(t *testing.T, ctx *context.T, ns namespace.T, name string, want ...string) {
@@ -197,6 +221,7 @@
if err := s.ServeDispatcher(mountPoint, disp); err != nil {
boom(t, "Failed to serve mount table at %s: %s", mountPoint, err)
}
+ t.Logf("server %q -> %s", eps[0].Name(), mountPoint)
return &serverEntry{mountPoint: mountPoint, server: s, endpoint: eps[0], name: eps[0].Name()}
}
@@ -267,7 +292,9 @@
// We directly mount baz into the mt4/foo mount table.
globalMP := naming.JoinAddressName(mts["mt4/foo"].name, "baz")
mts["baz"] = runMT(t, ctx, "baz")
- if err := ns.Mount(ctx, globalMP, mts["baz"].name, ttl); err != nil {
+ sctx, done := context.WithTimeout(ctx, 2*time.Minute)
+ defer done()
+ if err := ns.Mount(sctx, globalMP, mts["baz"].name, ttl); err != nil {
boom(t, "Failed to Mount %s: %s", globalMP, err)
}
}
@@ -645,6 +672,9 @@
// Intermediate mounttables should be authenticated.
mt := runMT(t, mtCtx, "mt")
+ defer func() {
+ mt.server.Stop()
+ }()
// Mount a server on "mt".
if err := mount("mt/server", serverEndpoint, time.Minute, naming.ReplaceMount(true)); err != nil {
t.Error(err)
@@ -736,6 +766,8 @@
_, ctx, cleanup := createContexts(t)
defer cleanup()
root := runMT(t, ctx, "")
+ defer func() { root.server.Stop() }()
+
ns := v23.GetNamespace(ctx)
ns.SetRoots(root.name)
@@ -750,6 +782,7 @@
if err := server.Serve("leaf", &leafObject{}, nil); err != nil {
boom(t, "server.Serve failed: %s", err)
}
+ defer server.Stop()
mountEntry, err := ns.Resolve(ctx, "leaf")
if err != nil {
diff --git a/profiles/internal/naming/namespace/glob.go b/profiles/internal/naming/namespace/glob.go
index 267330a..732b48c 100644
--- a/profiles/internal/naming/namespace/glob.go
+++ b/profiles/internal/naming/namespace/glob.go
@@ -9,14 +9,15 @@
"strings"
"sync"
- "v.io/x/ref/lib/glob"
+ "v.io/x/lib/vlog"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/verror"
- "v.io/x/lib/vlog"
+
+ "v.io/x/ref/lib/glob"
)
type tracks struct {
diff --git a/profiles/internal/naming/namespace/perms_test.go b/profiles/internal/naming/namespace/perms_test.go
index 93a30e5..0719656 100644
--- a/profiles/internal/naming/namespace/perms_test.go
+++ b/profiles/internal/naming/namespace/perms_test.go
@@ -239,6 +239,7 @@
if err != nil {
t.Fatalf("v23.NewServer failed: %v", err)
}
+ defer server.Stop()
if _, err := server.Listen(v23.GetListenSpec(rootCtx)); err != nil {
t.Fatalf("Failed to Listen: %s", err)
}
diff --git a/profiles/internal/rpc/client.go b/profiles/internal/rpc/client.go
index 4e61ff9..a032018 100644
--- a/profiles/internal/rpc/client.go
+++ b/profiles/internal/rpc/client.go
@@ -565,7 +565,7 @@
go func() {
select {
case <-doneChan:
- vtrace.GetSpan(fc.ctx).Annotate("Cancelled")
+ vtrace.GetSpan(fc.ctx).Annotate("Canceled")
fc.flow.Cancel()
case <-fc.flow.Closed():
}
@@ -898,8 +898,6 @@
defer vlog.LogCallf("itemptr=")("") // AUTO-GENERATED, DO NOT EDIT, MUST BE FIRST STATEMENT
switch {
case fc.response.Error != nil:
- // TODO(cnicolaou): this will become a verror.E when we convert the
- // server.
return verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error)
case fc.response.EndStreamResults:
return io.EOF
@@ -912,8 +910,6 @@
return fc.close(berr)
}
if fc.response.Error != nil {
- // TODO(cnicolaou): this will become a verror.E when we convert the
- // server.
return verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error)
}
if fc.response.EndStreamResults {
diff --git a/services/internal/pathperms/permsaccess.go b/services/internal/pathperms/permsaccess.go
index 6e463ac..81c6b76 100644
--- a/services/internal/pathperms/permsaccess.go
+++ b/services/internal/pathperms/permsaccess.go
@@ -49,7 +49,7 @@
}
// Get returns the Permissions from the data file in dir.
-func (store PathStore) Get(dir string) (access.Permissions, string, error) {
+func (store *PathStore) Get(dir string) (access.Permissions, string, error) {
permspath := filepath.Join(dir, permsName)
sigpath := filepath.Join(dir, sigName)
defer store.lockPath(dir)()
@@ -57,7 +57,7 @@
}
// TODO(rjkroege): Improve lock handling.
-func (store PathStore) lockPath(dir string) func() {
+func (store *PathStore) lockPath(dir string) func() {
store.lk.Lock()
lck, contains := store.pthlks[dir]
if !contains {
@@ -107,7 +107,7 @@
// Set writes the specified Permissions to the provided directory with
// enforcement of version synchronization mechanism and locking.
-func (store PathStore) Set(dir string, perms access.Permissions, version string) error {
+func (store *PathStore) Set(dir string, perms access.Permissions, version string) error {
return store.SetShareable(dir, perms, version, false)
}
@@ -115,7 +115,7 @@
// directory with enforcement of version synchronization mechanism and
// locking with file modes that will give the application read-only
// access to the permissions file.
-func (store PathStore) SetShareable(dir string, perms access.Permissions, version string, shareable bool) error {
+func (store *PathStore) SetShareable(dir string, perms access.Permissions, version string, shareable bool) error {
permspath := filepath.Join(dir, permsName)
sigpath := filepath.Join(dir, sigName)
defer store.lockPath(dir)()
@@ -187,7 +187,7 @@
return nil
}
-func (store PathStore) PermsForPath(path string) (access.Permissions, bool, error) {
+func (store *PathStore) PermsForPath(path string) (access.Permissions, bool, error) {
perms, _, err := store.Get(path)
if os.IsNotExist(err) {
return nil, true, nil
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
index 3b87098..4fb98d6 100644
--- a/services/mounttable/mounttablelib/mounttable.go
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -28,6 +28,7 @@
)
const pkgPath = "v.io/x/ref/services/mounttable/mounttablelib"
+const defaultMaxNodesPerUser = 1000
var (
errMalformedAddress = verror.Register(pkgPath+".errMalformedAddress", verror.NoRetry, "{1:}{2:} malformed address {3} for mounted server {4}{:_}")
@@ -36,6 +37,8 @@
errCantDeleteRoot = verror.Register(pkgPath+".errCantDeleteRoot", verror.NoRetry, "{1:}{2:} cannot delete root node{:_}")
errNotEmpty = verror.Register(pkgPath+".errNotEmpty", verror.NoRetry, "{1:}{2:} cannot delete {3}: has children{:_}")
errNamingLoop = verror.Register(pkgPath+".errNamingLoop", verror.NoRetry, "{1:}{2:} Loop in namespace{:_}")
+ errTooManyNodes = verror.Register(pkgPath+".errTooManyNodes", verror.NoRetry, "{1:}{2:} User has exceeded his node limit {:_}")
+ errNoSharedRoot = verror.Register(pkgPath+".errNoSharedRoot", verror.NoRetry, "{1:}{2:} Server and User share no blessing root {:_}")
)
var (
@@ -51,19 +54,23 @@
)
type persistence interface {
- persistPerms(name string, perm *VersionedPermissions) error
+ persistPerms(name, creator string, perm *VersionedPermissions) error
persistDelete(name string) error
close()
}
// mountTable represents a namespace. One exists per server instance.
type mountTable struct {
- root *node
- superUsers access.AccessList
- persisting bool
- persist persistence
- nodeCounter *stats.Integer
- serverCounter *stats.Integer
+ sync.Mutex
+ root *node
+ superUsers access.AccessList
+ persisting bool
+ persist persistence
+ nodeCounter *stats.Integer
+ serverCounter *stats.Integer
+ perUserNodeCounter *stats.Map
+ maxNodesPerUser int64
+ userPrefixes []string
}
var _ rpc.Dispatcher = (*mountTable)(nil)
@@ -93,6 +100,7 @@
vPerms *VersionedPermissions
permsTemplate access.Permissions
explicitPermissions bool
+ creator string
}
const templateVar = "%%"
@@ -109,9 +117,11 @@
// statsPrefix is the prefix for for exported statistics objects.
func NewMountTableDispatcher(permsFile, persistDir, statsPrefix string) (rpc.Dispatcher, error) {
mt := &mountTable{
- root: new(node),
- nodeCounter: stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
- serverCounter: stats.NewInteger(naming.Join(statsPrefix, "num-mounted-servers")),
+ root: new(node),
+ nodeCounter: stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
+ serverCounter: stats.NewInteger(naming.Join(statsPrefix, "num-mounted-servers")),
+ perUserNodeCounter: stats.NewMap(naming.Join(statsPrefix, "num-nodes-per-user")),
+ maxNodesPerUser: defaultMaxNodesPerUser,
}
mt.root.parent = mt.newNode() // just for its lock
if persistDir != "" {
@@ -140,6 +150,7 @@
if n == nil {
return
}
+ mt.credit(n)
nodeCount := int64(0)
serverCount := int64(0)
queue := []*node{n}
@@ -151,6 +162,7 @@
for _, ch := range n.children {
ch.Lock() // Keep locked until it is deleted.
queue = append(queue, ch)
+ mt.credit(ch)
}
}
@@ -315,8 +327,14 @@
return nil, nil, err
}
}
+ // Obey account limits.
+ creator, err := mt.debit(ctx, call)
+ if err != nil {
+ return nil, nil, err
+ }
// At this point cur is still locked, OK to use and change it.
next := mt.newNode()
+ next.creator = creator
next.parent = cur
if cur.permsTemplate != nil {
next.vPerms = createVersionedPermissionsFromTemplate(cur.permsTemplate, e)
@@ -805,7 +823,7 @@
n.vPerms, err = n.vPerms.Set(ctx, version, perms)
if err == nil {
if mt.persisting {
- mt.persist.persistPerms(ms.name, n.vPerms)
+ mt.persist.persistPerms(ms.name, n.creator, n.vPerms)
}
n.explicitPermissions = true
}
@@ -829,3 +847,23 @@
version, perms := n.vPerms.Get()
return perms, version, nil
}
+
+// credit user for node deletion.
+func (mt *mountTable) credit(n *node) {
+ mt.perUserNodeCounter.Incr(n.creator, -1)
+}
+
+// debit user for node creation.
+func (mt *mountTable) debit(ctx *context.T, call security.Call) (string, error) {
+ creator := mt.pickCreator(ctx, call)
+ count, ok := mt.perUserNodeCounter.Incr(creator, 1).(int64)
+ if !ok {
+ return "", verror.New(errTooManyNodes, ctx)
+ }
+ // If we have no prefixes defining users, don't bother with checking per user limits.
+ if len(mt.userPrefixes) != 0 && count > mt.maxNodesPerUser {
+ mt.perUserNodeCounter.Incr(creator, -1)
+ return "", verror.New(errTooManyNodes, ctx)
+ }
+ return creator, nil
+}
diff --git a/services/mounttable/mounttablelib/mounttable_test.go b/services/mounttable/mounttablelib/mounttable_test.go
index 5f0da6e..6d27b94 100644
--- a/services/mounttable/mounttablelib/mounttable_test.go
+++ b/services/mounttable/mounttablelib/mounttable_test.go
@@ -5,6 +5,7 @@
package mounttablelib
import (
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -25,6 +26,7 @@
"v.io/v23/vdl"
"v.io/x/lib/vlog"
+ libstats "v.io/x/ref/lib/stats"
"v.io/x/ref/services/debug/debuglib"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
@@ -177,7 +179,7 @@
}
}
-func newMT(t *testing.T, permsFile, persistDir string, rootCtx *context.T) (rpc.Server, string) {
+func newMT(t *testing.T, permsFile, persistDir, statsDir string, rootCtx *context.T) (rpc.Server, string) {
reservedDisp := debuglib.NewDispatcher(vlog.Log.LogDir, nil)
ctx := v23.WithReservedNameDispatcher(rootCtx, reservedDisp)
server, err := v23.NewServer(ctx, options.ServesMountTable(true))
@@ -185,7 +187,7 @@
boom(t, "r.NewServer: %s", err)
}
// Add mount table service.
- mt, err := NewMountTableDispatcher(permsFile, persistDir, "mounttable")
+ mt, err := NewMountTableDispatcher(permsFile, persistDir, statsDir)
if err != nil {
boom(t, "NewMountTableDispatcher: %v", err)
}
@@ -227,7 +229,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- mt, mtAddr := newMT(t, "testdata/test.perms", "", rootCtx)
+ mt, mtAddr := newMT(t, "testdata/test.perms", "", "testMountTable", rootCtx)
defer mt.Stop()
collection, collectionAddr := newCollection(t, rootCtx)
defer collection.Stop()
@@ -397,7 +399,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", "", rootCtx)
+ server, estr := newMT(t, "", "", "testGlob", rootCtx)
defer server.Stop()
// set up a mount space
@@ -444,7 +446,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", "testAccessListTemplate", rootCtx)
defer server.Stop()
fakeServer := naming.JoinAddressName(estr, "quux")
@@ -457,13 +459,67 @@
doMount(t, aliceCtx, estr, "users/alice", fakeServer, true)
doMount(t, bobCtx, estr, "users/bob", fakeServer, true)
doMount(t, rootCtx, estr, "users/root", fakeServer, true)
+
+ // Make sure the counter works.
+ doUnmount(t, aliceCtx, estr, "users/alice", "", true)
+ doUnmount(t, bobCtx, estr, "users/bob", "", true)
+ doUnmount(t, rootCtx, estr, "users/root", "", true)
+ perms := access.Permissions{"Admin": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}}}
+ doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d", perms, "", true)
+ doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d", perms, "", true)
+
+ // Do we obey limits?
+ for i := 0; i < defaultMaxNodesPerUser-5; i++ {
+ node := fmt.Sprintf("users/alice/a/b/c/d/%d", i)
+ doSetPermissions(t, aliceCtx, estr, node, perms, "", true)
+ }
+ doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d/straw", perms, "", false)
+
+ // See if the stats numbers are correct.
+ testcases := []struct {
+ key string
+ expected interface{}
+ }{
+ {"alice", int64(defaultMaxNodesPerUser)},
+ {"bob", int64(0)},
+ {"root", int64(0)},
+ {localUser, int64(3)},
+ }
+ for _, tc := range testcases {
+ name := "testAccessListTemplate/num-nodes-per-user/" + tc.key
+ got, err := libstats.Value(name)
+ if err != nil {
+ t.Errorf("unexpected error getting map entry for %s: %s", name, err)
+ }
+ if got != tc.expected {
+ t.Errorf("unexpected getting map entry for %s. Got %v, want %v", name, got, tc.expected)
+ }
+ }
+}
+
+func getUserNodeCounts(t *testing.T) (counts map[string]int32) {
+ s, err := libstats.Value("mounttable/num-nodes-per-user")
+ if err != nil {
+ boom(t, "Can't get mounttable statistics")
+ }
+ // This string is a json encoded map. Decode.
+ switch v := s.(type) {
+ default:
+ boom(t, "Wrong type for mounttable statistics")
+ case string:
+ err = json.Unmarshal([]byte(v), &counts)
+ if err != nil {
+ boom(t, "Can't unmarshal mounttable statistics")
+ }
+ }
+ return
}
func TestGlobAccessLists(t *testing.T) {
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", "testGlobAccessLists", rootCtx)
defer server.Stop()
// set up a mount space
@@ -496,7 +552,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", "", rootCtx)
+ server, estr := newMT(t, "", "", "testCleanup", rootCtx)
defer server.Stop()
// Set up one mount.
@@ -524,7 +580,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", "testDelete", rootCtx)
defer server.Stop()
// set up a mount space
@@ -551,7 +607,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", "", rootCtx)
+ server, estr := newMT(t, "", "", "testerverFormat", rootCtx)
defer server.Stop()
doMount(t, rootCtx, estr, "endpoint", naming.JoinAddressName(estr, "life/on/the/mississippi"), true)
@@ -565,7 +621,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", "", rootCtx)
+ server, estr := newMT(t, "", "", "testExpiry", rootCtx)
defer server.Stop()
collection, collectionAddr := newCollection(t, rootCtx)
defer collection.Stop()
@@ -633,7 +689,7 @@
ft := NewFakeTimeClock()
setServerListClock(ft)
- server, estr := newMT(t, "", "", rootCtx)
+ server, estr := newMT(t, "", "", "mounttable", rootCtx)
defer server.Stop()
// Test flat tree
diff --git a/services/mounttable/mounttablelib/persist_test.go b/services/mounttable/mounttablelib/persist_test.go
index a6b936b..c6b7aca 100644
--- a/services/mounttable/mounttablelib/persist_test.go
+++ b/services/mounttable/mounttablelib/persist_test.go
@@ -25,7 +25,7 @@
}
defer os.RemoveAll(td)
fmt.Printf("temp persist dir %s\n", td)
- mt, mtAddr := newMT(t, "", td, rootCtx)
+ mt, mtAddr := newMT(t, "", td, "testPersistence", rootCtx)
perms1 := access.Permissions{
"Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
@@ -53,7 +53,7 @@
mt.Stop()
// Restart with the persisted data.
- mt, mtAddr = newMT(t, "", td, rootCtx)
+ mt, mtAddr = newMT(t, "", td, "testPersistence", rootCtx)
// Add root as Admin to each of the perms since the mounttable itself will.
perms1["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
diff --git a/services/mounttable/mounttablelib/persistentstore.go b/services/mounttable/mounttablelib/persistentstore.go
index 1dfd2f8..0cb614d 100644
--- a/services/mounttable/mounttablelib/persistentstore.go
+++ b/services/mounttable/mounttablelib/persistentstore.go
@@ -26,7 +26,8 @@
type storeElement struct {
N string // Name of affected node
V VersionedPermissions
- D bool // True if the subtree at N has been deleted
+ D bool // True if the subtree at N has been deleted
+ C string // Creator
}
// newPersistentStore will read the permissions log from the directory and apply them to the
@@ -132,6 +133,7 @@
elems := strings.Split(e.N, "/")
n, err := mt.findNode(nil, nil, elems, true, nil)
if n != nil || err == nil {
+ n.creator = e.C
if e.D {
mt.deleteNode(n.parent, elems[len(elems)-1])
vlog.VI(2).Infof("deleted %s", e.N)
@@ -152,7 +154,7 @@
// any duplicate or deleted entries disappear.
func (s *store) depthFirstPersist(n *node, name string) {
if n.explicitPermissions {
- s.persistPerms(name, n.vPerms)
+ s.persistPerms(name, n.creator, n.vPerms)
}
for nodeName, c := range n.children {
s.depthFirstPersist(c, path.Join(name, nodeName))
@@ -160,10 +162,10 @@
}
// persistPerms appends a changed permission to the log.
-func (s *store) persistPerms(name string, vPerms *VersionedPermissions) error {
+func (s *store) persistPerms(name, creator string, vPerms *VersionedPermissions) error {
s.l.Lock()
defer s.l.Unlock()
- e := storeElement{N: name, V: *vPerms}
+ e := storeElement{N: name, V: *vPerms, C: creator}
return s.enc.Encode(&e)
}
diff --git a/services/mounttable/mounttablelib/versionedpermissions.go b/services/mounttable/mounttablelib/versionedpermissions.go
index f68401f..867be02 100644
--- a/services/mounttable/mounttablelib/versionedpermissions.go
+++ b/services/mounttable/mounttablelib/versionedpermissions.go
@@ -8,6 +8,7 @@
"encoding/json"
"io"
"os"
+ "reflect"
"strconv"
"strings"
@@ -19,6 +20,12 @@
"v.io/x/lib/vlog"
)
+// Blessings can't include a comma so we use them in made up user ids. The following distinctions are
+// made so that we can account for them differently.
+const localUser = ",LOCAL," // a client that has our public key but no blessing from which we can extract a user name
+const blessedUser = ",BLESSED," // a client with blessings we trust but from which we can't extract a user name
+const unknownUser = ",UNKNOWN," // a client which presents no blessing we trust
+
// VersionedPermissions associates a Version with a Permissions
type VersionedPermissions struct {
V int32
@@ -45,8 +52,8 @@
}
}
b.P = perm
+ // Increment with possible wrap.
b.V++
- // Protect against wrap.
if b.V < 0 {
b.V = 0
}
@@ -132,6 +139,12 @@
vlog.VI(2).Infof("added perms %v to %s", perms, name)
if isPattern {
n.permsTemplate = perms
+ // Save the pattern prefix as a prefix describing a user.
+ prefix := strings.Join(elems[:len(elems)-1], "/")
+ if prefix != "" {
+ prefix += "/"
+ }
+ mt.userPrefixes = append(mt.userPrefixes, prefix)
} else {
n.vPerms, _ = n.vPerms.Set(nil, "", perms)
n.explicitPermissions = true
@@ -143,3 +156,38 @@
}
return nil
}
+
+// pickCreator returns a string matching the blessing of the user performing the creation. We do this using
+// the user prefixes found when parsing the config. Eventually we may need a better way to define user
+// prefixes.
+//
+// TODO(p): readdress this argument after we have some experience with real users.
+func (mt *mountTable) pickCreator(ctx *context.T, call security.Call) string {
+ // For each blessing, look for one that has a matching user prefix. The creator is the perfix
+ // plus one more element.
+ //
+ // The prefixes themselves come from the templates in the config that constrain node names to
+ // match the user.
+ blessings, _ := security.RemoteBlessingNames(ctx, call)
+ for _, b := range blessings {
+ for _, p := range mt.userPrefixes {
+ sb := string(b)
+ if !strings.HasPrefix(sb, p) {
+ continue
+ }
+ suffix := strings.TrimPrefix(sb, p)
+ elems := strings.Split(suffix, "/")
+ return p + elems[0]
+ }
+ }
+ if ctx == nil || call == nil {
+ return localUser
+ }
+ if l, r := call.LocalBlessings().PublicKey(), call.RemoteBlessings().PublicKey(); l != nil && reflect.DeepEqual(l, r) {
+ return localUser
+ }
+ if len(blessings) > 0 {
+ return blessedUser
+ }
+ return unknownUser
+}