veyron/services/mgmt/application: add support for per envelope acls
This change extends the applicationd daemon with per envelope ACLs.
Change-Id: Ib0ab7efa39f82723418e3f959877d05e6e22eb08
diff --git a/services/mgmt/application/applicationd/main.go b/services/mgmt/application/applicationd/main.go
index 1a4b6c6..a09993e 100644
--- a/services/mgmt/application/applicationd/main.go
+++ b/services/mgmt/application/applicationd/main.go
@@ -9,7 +9,6 @@
"v.io/core/veyron/lib/signals"
"v.io/core/veyron/profiles/roaming"
- vflag "v.io/core/veyron/security/flag"
"v.io/core/veyron/services/mgmt/application/impl"
)
@@ -37,7 +36,7 @@
}
defer server.Stop()
- dispatcher, err := impl.NewDispatcher(*store, vflag.NewAuthorizerOrDie())
+ dispatcher, err := impl.NewDispatcher(*store)
if err != nil {
vlog.Fatalf("NewDispatcher() failed: %v", err)
}
diff --git a/services/mgmt/application/impl/acl_test.go b/services/mgmt/application/impl/acl_test.go
new file mode 100644
index 0000000..9c0d6ab
--- /dev/null
+++ b/services/mgmt/application/impl/acl_test.go
@@ -0,0 +1,413 @@
+package impl_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "syscall"
+ "testing"
+
+ "v.io/core/veyron2"
+ "v.io/core/veyron2/context"
+ "v.io/core/veyron2/naming"
+ "v.io/core/veyron2/rt"
+ "v.io/core/veyron2/security"
+ "v.io/core/veyron2/services/mgmt/application"
+ "v.io/core/veyron2/services/security/access"
+ "v.io/core/veyron2/vdl/vdlutil"
+ "v.io/core/veyron2/verror"
+ "v.io/core/veyron2/vlog"
+
+ "v.io/core/veyron/lib/modules"
+ "v.io/core/veyron/lib/signals"
+ "v.io/core/veyron/lib/testutil"
+ tsecurity "v.io/core/veyron/lib/testutil/security"
+ "v.io/core/veyron/services/mgmt/application/impl"
+ mgmttest "v.io/core/veyron/services/mgmt/lib/testutil"
+ "v.io/core/veyron/services/mgmt/repository"
+)
+
+const (
+ repoCmd = "repository"
+)
+
+var globalRT veyron2.Runtime
+var globalCtx *context.T
+
+// This is also a modules world.
+// Insert necessary code here to be a modules test.
+func init() {
+ // TODO(rjkroege): Remove when vom2 is ready.
+ vdlutil.Register(&naming.VDLMountedServer{})
+
+ modules.RegisterChild(repoCmd, "", appRepository)
+ testutil.Init()
+
+ var err error
+ if globalRT, err = rt.New(); err != nil {
+ panic(err)
+ }
+ globalCtx = globalRT.NewContext()
+ globalRT.Namespace().CacheCtl(naming.DisableCache(true))
+}
+
+// TestHelperProcess is the entrypoint for the modules commands in a
+// a test subprocess.
+func TestHelperProcess(t *testing.T) {
+ modules.DispatchInTest()
+}
+
+func appRepository(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ args = args[1:]
+ if len(args) < 2 {
+ vlog.Fatalf("repository expected at least name and store arguments and optionally ACL flags per TaggedACLMapFromFlag")
+ }
+ publishName := args[0]
+ storedir := args[1]
+
+ defer fmt.Fprintf(stdout, "%v terminating\n", publishName)
+ defer vlog.VI(1).Infof("%v terminating", publishName)
+ defer globalRT.Cleanup()
+ server, endpoint := mgmttest.NewServer(globalRT)
+ defer server.Stop()
+
+ name := naming.JoinAddressName(endpoint, "")
+ vlog.VI(1).Infof("applicationd name: %v", name)
+
+ dispatcher, err := impl.NewDispatcher(storedir)
+ if err != nil {
+ vlog.Fatalf("Failed to create repository dispatcher: %v", err)
+ }
+ if err := server.ServeDispatcher(publishName, dispatcher); err != nil {
+ vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
+ }
+
+ fmt.Fprintf(stdout, "ready:%d\n", os.Getpid())
+ <-signals.ShutdownOnSignals(globalCtx)
+
+ return nil
+}
+
+func TestApplicationUpdateACL(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // setup mock up directory to put state in
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // By default, selfRT and otherRT will have blessings generated based on the
+ // username/machine name running this process. Since these blessings will appear
+ // in ACLs, give them recognizable names.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ if err := idp.Bless(otherRT.Principal(), "other"); err != nil {
+ t.Fatal(err)
+ }
+
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make server credentials derived from the test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ otherStub := repository.ApplicationClient("repo/search/v1", otherRT.Client())
+
+ // Create example envelopes.
+ envelopeV1 := application.Envelope{
+ Args: []string{"--help"},
+ Env: []string{"DEBUG=1"},
+ Binary: "/veyron/name/of/binary",
+ }
+
+ // Envelope putting as other should fail.
+ // TODO(rjkroege): Validate that it is failed with permission denied.
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err == nil {
+ t.Fatalf("Put() wrongly didn't fail")
+ }
+
+ // Envelope putting as self should succeed.
+ selfStub := repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+
+ selfStub = repository.ApplicationClient("repo", selfRT.Client())
+ acl, etag, err := selfStub.GetACL(selfRT.NewContext())
+ if !verror.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("GetACL should have failed with ErrNotFound but was: %v", err)
+ }
+ if etag != "default" {
+ t.Fatalf("getACL expected:default, got:%v(%v)", etag, acl)
+ }
+ if acl != nil {
+ t.Fatalf("GetACL got %v, expected %v", acl, nil)
+ }
+
+ vlog.VI(2).Infof("self attempting to give other permission to update application")
+ newACL := make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ newACL.Add("root/other", string(tag))
+ }
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ acl, etag, err = selfStub.GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected := newACL
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+
+ // Envelope putting as other should now succeed.
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() wrongly failed: %v", err)
+ }
+
+ // Other takes control.
+ otherStub = repository.ApplicationClient("repo/", otherRT.Client())
+ acl, etag, err = otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL 2 should not have failed: %v", err)
+ }
+ acl["Admin"] = access.ACL{
+ In: []security.BlessingPattern{"root/other"},
+ NotIn: []string{}}
+ if err = otherStub.SetACL(otherRT.NewContext(), acl, etag); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Self is now locked out but other isn't.
+ if _, _, err = selfStub.GetACL(selfRT.NewContext()); err == nil {
+ t.Fatalf("GetACL should not have succeeded")
+ }
+ acl, _, err = otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected = access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/other"},
+ NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Write": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}}}
+
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+}
+
+func TestPerAppACL(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // setup mock up directory to put state in
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // By default, selfRT and otherRT will have blessings generated based on the
+ // username/machine name running this process. Since these blessings will appear
+ // in ACLs, give them recognizable names.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ if err := idp.Bless(otherRT.Principal(), "other"); err != nil {
+ t.Fatal(err)
+ }
+
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make a server with the same credential as test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ // Create example envelope.
+ envelopeV1 := application.Envelope{
+ Args: []string{"--help"},
+ Env: []string{"DEBUG=1"},
+ Binary: "/veyron/name/of/binary",
+ }
+
+ // Upload the envelope at two different names.
+ selfStub := repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+ selfStub = repository.ApplicationClient("repo/search/v2", selfRT.Client())
+ if err := selfStub.Put(selfRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+
+ // Self can access ACLs but other can't.
+ for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+ selfStub = repository.ApplicationClient(path, selfRT.Client())
+ acl, etag, err := selfStub.GetACL(selfRT.NewContext())
+ if !verror.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("GetACL should have failed with ErrNotFound but was: %v", err)
+ }
+ if etag != "default" {
+ t.Fatalf("GetACL expected:default, got:%v(%v)", etag, acl)
+ }
+ if acl != nil {
+ t.Fatalf("GetACL got %v, expected %v", acl, nil)
+ }
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+
+ // Self gives other full access only to repo/search/v1.
+ selfStub = repository.ApplicationClient("repo/search/v1", selfRT.Client())
+ newACL := make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ newACL.Add("root/other", string(tag))
+ }
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Other can now access this location.
+ otherStub := repository.ApplicationClient("repo/search/v1", otherRT.Client())
+ acl, _, err := otherStub.GetACL(otherRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Read": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Write": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Debug": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}},
+ "Resolve": access.ACL{In: []security.BlessingPattern{"root/other",
+ "root/self"},
+ NotIn: []string{}}}
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+
+ // But other locations should be unaffected and other cannot access.
+ for _, path := range []string{"repo/search", "repo/search/v2"} {
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+
+ // Self gives other write perms on base.
+ selfStub = repository.ApplicationClient("repo/", selfRT.Client())
+ newACL = make(access.TaggedACLMap)
+ for _, tag := range access.AllTypicalTags() {
+ newACL.Add("root/self", string(tag))
+ }
+ newACL["Write"] = access.ACL{In: []security.BlessingPattern{"root/other", "root/self"}}
+ if err := selfStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL failed: %v", err)
+ }
+
+ // Other can now upload an envelope at both locations.
+ for _, path := range []string{"repo/search/v1", "repo/search/v2"} {
+ otherStub = repository.ApplicationClient(path, otherRT.Client())
+ if err := otherStub.Put(otherRT.NewContext(), []string{"base"}, envelopeV1); err != nil {
+ t.Fatalf("Put() failed: %v", err)
+ }
+ }
+
+ // But self didn't give other ACL modification permissions.
+ for _, path := range []string{"repo/search", "repo/search/v2"} {
+ otherStub := repository.ApplicationClient(path, otherRT.Client())
+ if _, _, err := otherStub.GetACL(otherRT.NewContext()); err == nil {
+ t.Fatalf("GetACL didn't fail for other when it should have.")
+ }
+ }
+}
+
+func TestInitialACLSet(t *testing.T) {
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
+ defer deferFn()
+
+ // Setup mock up directory to put state in.
+ storedir, cleanup := mgmttest.SetupRootDir(t, "application")
+ defer cleanup()
+
+ selfRT := globalRT
+ otherRT := mgmttest.NewRuntime(t, globalRT)
+ defer otherRT.Cleanup()
+ idp := tsecurity.NewIDProvider("root")
+
+ // Make a recognizable principal name.
+ if err := idp.Bless(selfRT.Principal(), "self"); err != nil {
+ t.Fatal(err)
+ }
+ crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "repo")
+ defer os.RemoveAll(crDir)
+
+ // Make an TAM for use on the command line.
+ expected := access.TaggedACLMap{
+ "Admin": access.ACL{
+ In: []security.BlessingPattern{"root/rubberchicken",
+ "root/self"},
+ NotIn: []string{},
+ },
+ }
+
+ b := new(bytes.Buffer)
+ if err := expected.WriteTo(b); err != nil {
+ t.Fatal(err)
+ }
+
+ // Start a server with the same credential as test harness.
+ _, nms := mgmttest.RunShellCommand(t, sh, crEnv, repoCmd, "--veyron.acl.literal", b.String(), "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
+ // It should have the correct starting ACLs from the command line.
+ selfStub := repository.ApplicationClient("repo", selfRT.Client())
+ acl, _, err := selfStub.GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL should not have failed: %v", err)
+ }
+ if got := acl; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+ t.Errorf("got %#v, exected %#v ", got, expected)
+ }
+}
diff --git a/services/mgmt/application/impl/dispatcher.go b/services/mgmt/application/impl/dispatcher.go
index eaaf8d2..a825137 100644
--- a/services/mgmt/application/impl/dispatcher.go
+++ b/services/mgmt/application/impl/dispatcher.go
@@ -3,34 +3,87 @@
import (
"path/filepath"
- "v.io/core/veyron/services/mgmt/repository"
-
- "v.io/core/veyron/services/mgmt/lib/fs"
- "v.io/core/veyron2/ipc"
+ "v.io/core/veyron2/naming"
"v.io/core/veyron2/security"
+ "v.io/core/veyron2/services/security/access"
+ "v.io/core/veyron2/verror"
+ "v.io/core/veyron2/vlog"
+
+ "v.io/core/veyron/security/flag"
+ "v.io/core/veyron/services/mgmt/lib/fs"
+ "v.io/core/veyron/services/mgmt/repository"
)
// dispatcher holds the state of the application repository dispatcher.
type dispatcher struct {
store *fs.Memstore
- auth security.Authorizer
storeRoot string
}
-var _ ipc.Dispatcher = (*dispatcher)(nil)
-
// NewDispatcher is the dispatcher factory. storeDir is a path to a directory in which to
// serialize the applicationd state.
-func NewDispatcher(storeDir string, authorizer security.Authorizer) (*dispatcher, error) {
+func NewDispatcher(storeDir string) (*dispatcher, error) {
store, err := fs.NewMemstore(filepath.Join(storeDir, "applicationdstate.db"))
if err != nil {
return nil, err
}
- return &dispatcher{store: store, storeRoot: storeDir, auth: authorizer}, nil
+
+ acls, err := flag.TaggedACLMapFromFlag()
+ if err != nil {
+ return nil, err
+ }
+ if acls != nil {
+ store.Lock()
+ defer store.Unlock()
+
+ // (Re)set the root ACLs.
+ path := naming.Join("/acls", "data")
+ _, tag, err := getACL(store, path)
+ if !verror.Is(err, ErrNotFound.ID) {
+ return nil, err
+ }
+ if err := setACL(store, path, acls, tag); err != nil {
+ return nil, err
+ }
+ }
+
+ return &dispatcher{store: store, storeRoot: storeDir}, nil
}
// DISPATCHER INTERFACE IMPLEMENTATION
+// getAuthorizer searches the provided list of paths in the Memstore hierarchy
+// for an TaggedACLMap and uses it to produce an authorizer or returns nil
+// to get a nil Authorizer.
+func getAuthorizer(store *fs.Memstore, paths []string) (security.Authorizer, error) {
+ for _, p := range paths {
+ if tam, _, err := getACL(store, p); err == nil {
+ auth, err := access.TaggedACLAuthorizer(tam, access.TypicalTagType())
+ if err != nil {
+ vlog.Errorf("Successfully obtained an ACL from Memstore but TaggedACLAuthorizer couldn't use it: %v", err)
+ return nil, err
+ }
+ return auth, nil
+ } else if !verror.Is(err, ErrNotFound.ID) {
+ vlog.Errorf("Internal error obtaining ACL from Memstore: %v", err)
+ }
+ }
+ return nil, nil
+}
+
func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
- return repository.ApplicationServer(NewApplicationService(d.store, d.storeRoot, suffix)), d.auth, nil
+ app, version, _ := parse(nil, suffix)
+ // TODO(rjkroege@google.com): Implement ACL inheritance.
+ // Construct the search hierarchy for ACLs.
+ sp := []string{
+ naming.Join("/acls", app, version, "data"),
+ naming.Join("/acls", app, "data"),
+ naming.Join("/acls", "data"),
+ }
+
+ auth, err := getAuthorizer(d.store, sp)
+ if err != nil {
+ return nil, nil, err
+ }
+ return repository.ApplicationServer(NewApplicationService(d.store, d.storeRoot, suffix)), auth, nil
}
diff --git a/services/mgmt/application/impl/impl_test.go b/services/mgmt/application/impl/impl_test.go
index 90f2c32..52a0dd1 100644
--- a/services/mgmt/application/impl/impl_test.go
+++ b/services/mgmt/application/impl/impl_test.go
@@ -1,4 +1,4 @@
-package impl
+package impl_test
import (
"io/ioutil"
@@ -6,28 +6,21 @@
"reflect"
"testing"
- "v.io/core/veyron2"
"v.io/core/veyron2/naming"
- "v.io/core/veyron2/rt"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/verror2"
"v.io/core/veyron/lib/testutil"
- "v.io/core/veyron/profiles"
+ _ "v.io/core/veyron/profiles/static"
+ "v.io/core/veyron/services/mgmt/application/impl"
+ mgmttest "v.io/core/veyron/services/mgmt/lib/testutil"
"v.io/core/veyron/services/mgmt/repository"
)
// TestInterface tests that the implementation correctly implements
// the Application interface.
func TestInterface(t *testing.T) {
- ctx := runtime.NewContext()
-
- // Setup and start the application repository server.
- server, err := runtime.NewServer()
- if err != nil {
- t.Fatalf("NewServer() failed: %v", err)
- }
- defer server.Stop()
+ ctx := globalRT.NewContext()
dir, prefix := "", ""
store, err := ioutil.TempDir(dir, prefix)
@@ -35,24 +28,22 @@
t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
}
defer os.RemoveAll(store)
- dispatcher, err := NewDispatcher(store, nil)
+ dispatcher, err := impl.NewDispatcher(store)
if err != nil {
- t.Fatalf("NewDispatcher() failed: %v", err)
+ t.Fatalf("impl.NewDispatcher() failed: %v", err)
}
- endpoints, err := server.Listen(profiles.LocalListenSpec)
- if err != nil {
- t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
- }
+ server, endpoint := mgmttest.NewServer(globalRT)
+ defer server.Stop()
+
if err := server.ServeDispatcher("", dispatcher); err != nil {
t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
}
- endpoint := endpoints[0]
// Create client stubs for talking to the server.
- stub := repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search"))
- stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v1"))
- stubV2 := repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v2"))
+ stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
+ stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
+ stubV2 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v2"))
// Create example envelopes.
envelopeV1 := application.Envelope{
@@ -73,8 +64,8 @@
if err := stubV2.Put(ctx, []string{"base"}, envelopeV2); err != nil {
t.Fatalf("Put() failed: %v", err)
}
- if err := stub.Put(ctx, []string{"base", "media"}, envelopeV1); err == nil || !verror2.Is(err, errInvalidSuffix.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errInvalidSuffix, err)
+ if err := stub.Put(ctx, []string{"base", "media"}, envelopeV1); err == nil || !verror2.Is(err, impl.ErrInvalidSuffix.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrInvalidSuffix, err)
}
// Test Match(), trying to retrieve both existing and non-existing
@@ -92,18 +83,18 @@
if !reflect.DeepEqual(envelopeV1, output) {
t.Fatalf("Unexpected output: expected %v, got %v", envelopeV1, output)
}
- if _, err := stubV2.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if _, err := stubV2.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
- if _, err := stubV2.Match(ctx, []string{}); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if _, err := stubV2.Match(ctx, []string{}); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
- if _, err := stub.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, errInvalidSuffix.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errInvalidSuffix, err)
+ if _, err := stub.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, impl.ErrInvalidSuffix.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrInvalidSuffix, err)
}
// Test Glob
- matches, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint.String(), ""), "...")
+ matches, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
if err != nil {
t.Errorf("Unexpected Glob error: %v", err)
}
@@ -125,14 +116,14 @@
if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
t.Fatalf("Match() failed: %v", err)
}
- if err := stubV1.Remove(ctx, "base"); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if err := stubV1.Remove(ctx, "base"); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
if err := stub.Remove(ctx, "base"); err != nil {
t.Fatalf("Remove() failed: %v", err)
}
- if err := stubV2.Remove(ctx, "media"); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if err := stubV2.Remove(ctx, "media"); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
if err := stubV1.Remove(ctx, "media"); err != nil {
t.Fatalf("Remove() failed: %v", err)
@@ -140,14 +131,14 @@
// Finally, use Match() to test that Remove really removed the
// application envelopes.
- if _, err := stubV1.Match(ctx, []string{"base"}); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if _, err := stubV1.Match(ctx, []string{"base"}); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
- if _, err := stubV1.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if _, err := stubV1.Match(ctx, []string{"media"}); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
- if _, err := stubV2.Match(ctx, []string{"base"}); err == nil || !verror2.Is(err, errNotFound.ID) {
- t.Fatalf("Unexpected error: expected %v, got %v", errNotFound, err)
+ if _, err := stubV2.Match(ctx, []string{"base"}); err == nil || !verror2.Is(err, impl.ErrNotFound.ID) {
+ t.Fatalf("Unexpected error: expected %v, got %v", impl.ErrNotFound, err)
}
// Shutdown the application repository server.
@@ -156,22 +147,7 @@
}
}
-var runtime veyron2.Runtime
-
-func init() {
- var err error
- runtime, err = rt.New()
- if err != nil {
- panic(err)
- }
-}
-
func TestPreserveAcrossRestarts(t *testing.T) {
- server, err := runtime.NewServer()
- if err != nil {
- t.Fatalf("NewServer() failed: %v", err)
- }
- defer server.Stop()
dir, prefix := "", ""
storedir, err := ioutil.TempDir(dir, prefix)
if err != nil {
@@ -179,22 +155,20 @@
}
defer os.RemoveAll(storedir)
- dispatcher, err := NewDispatcher(storedir, nil)
+ dispatcher, err := impl.NewDispatcher(storedir)
if err != nil {
- t.Fatalf("NewDispatcher() failed: %v", err)
+ t.Fatalf("impl.NewDispatcher() failed: %v", err)
}
- endpoints, err := server.Listen(profiles.LocalListenSpec)
- if err != nil {
- t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
- }
- endpoint := endpoints[0]
+ server, endpoint := mgmttest.NewServer(globalRT)
+ defer server.Stop()
+
if err := server.ServeDispatcher("", dispatcher); err != nil {
t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
}
// Create client stubs for talking to the server.
- stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v1"))
+ stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
// Create example envelopes.
envelopeV1 := application.Envelope{
@@ -203,12 +177,12 @@
Binary: "/veyron/name/of/binary",
}
- if err := stubV1.Put(runtime.NewContext(), []string{"media"}, envelopeV1); err != nil {
+ if err := stubV1.Put(globalRT.NewContext(), []string{"media"}, envelopeV1); err != nil {
t.Fatalf("Put() failed: %v", err)
}
// There is content here now.
- output, err := stubV1.Match(runtime.NewContext(), []string{"media"})
+ output, err := stubV1.Match(globalRT.NewContext(), []string{"media"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "media", err)
}
@@ -218,29 +192,22 @@
server.Stop()
- // Setup and start a second application server in its place.
- server, err = runtime.NewServer()
+ // Setup and start a second application server.
+ dispatcher, err = impl.NewDispatcher(storedir)
if err != nil {
- t.Fatalf("NewServer() failed: %v", err)
+ t.Fatalf("impl.NewDispatcher() failed: %v", err)
}
+
+ server, endpoint = mgmttest.NewServer(globalRT)
defer server.Stop()
- dispatcher, err = NewDispatcher(storedir, nil)
- if err != nil {
- t.Fatalf("NewDispatcher() failed: %v", err)
- }
- endpoints, err = server.Listen(profiles.LocalListenSpec)
- if err != nil {
- t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
- }
- endpoint = endpoints[0]
if err := server.ServeDispatcher("", dispatcher); err != nil {
t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
}
- stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v1"))
+ stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
- output, err = stubV1.Match(runtime.NewContext(), []string{"media"})
+ output, err = stubV1.Match(globalRT.NewContext(), []string{"media"})
if err != nil {
t.Fatalf("Match(%v) failed: %v", "media", err)
}
diff --git a/services/mgmt/application/impl/service.go b/services/mgmt/application/impl/service.go
index 816a505..b0c6cc4 100644
--- a/services/mgmt/application/impl/service.go
+++ b/services/mgmt/application/impl/service.go
@@ -3,11 +3,14 @@
import (
"strings"
+ "v.io/core/veyron/services/mgmt/lib/acls"
"v.io/core/veyron/services/mgmt/lib/fs"
+
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/services/mgmt/application"
- "v.io/core/veyron2/verror2"
+ "v.io/core/veyron2/services/security/access"
+ verror "v.io/core/veyron2/verror2"
"v.io/core/veyron2/vlog"
)
@@ -27,9 +30,10 @@
const pkgPath = "v.io/core/veyron/services/mgmt/application/impl/"
var (
- errInvalidSuffix = verror2.Register(pkgPath+".invalidSuffix", verror2.NoRetry, "")
- errOperationFailed = verror2.Register(pkgPath+".operationFailed", verror2.NoRetry, "")
- errNotFound = verror2.Register(pkgPath+".notFound", verror2.NoRetry, "")
+ ErrInvalidSuffix = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
+ ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+ ErrNotFound = verror.Register(pkgPath+".NotFound", verror.NoRetry, "{1:}{2:} not found{:_}")
+ ErrInvalidBlessing = verror.Register(pkgPath+".InvalidBlessing", verror.NoRetry, "{1:}{2:} invalid blessing{:_}")
)
// NewApplicationService returns a new Application service implementation.
@@ -45,7 +49,7 @@
case 1:
return tokens[0], "", nil
default:
- return "", "", verror2.Make(errInvalidSuffix, context.Context())
+ return "", "", verror.Make(ErrInvalidSuffix, context.Context())
}
}
@@ -57,7 +61,7 @@
return empty, err
}
if version == "" {
- return empty, verror2.Make(errInvalidSuffix, context.Context())
+ return empty, verror.Make(ErrInvalidSuffix, context.Context())
}
i.store.Lock()
@@ -75,7 +79,7 @@
}
return envelope, nil
}
- return empty, verror2.Make(errNotFound, context.Context())
+ return empty, verror.Make(ErrNotFound, context.Context())
}
func (i *appRepoService) Put(context ipc.ServerContext, profiles []string, envelope application.Envelope) error {
@@ -85,7 +89,7 @@
return err
}
if version == "" {
- return verror2.Make(errInvalidSuffix, context.Context())
+ return verror.Make(ErrInvalidSuffix, context.Context())
}
i.store.Lock()
defer i.store.Unlock()
@@ -101,11 +105,11 @@
object := i.store.BindObject(path)
_, err := object.Put(context, envelope)
if err != nil {
- return verror2.Make(errOperationFailed, context.Context())
+ return verror.Make(ErrOperationFailed, context.Context())
}
}
if err := i.store.BindTransaction(tname).Commit(context); err != nil {
- return verror2.Make(errOperationFailed, context.Context())
+ return verror.Make(ErrOperationFailed, context.Context())
}
return nil
}
@@ -130,16 +134,16 @@
object := i.store.BindObject(path)
found, err := object.Exists(context)
if err != nil {
- return verror2.Make(errOperationFailed, context.Context())
+ return verror.Make(ErrOperationFailed, context.Context())
}
if !found {
- return verror2.Make(errNotFound, context.Context())
+ return verror.Make(ErrNotFound, context.Context())
}
if err := object.Remove(context); err != nil {
- return verror2.Make(errOperationFailed, context.Context())
+ return verror.Make(ErrOperationFailed, context.Context())
}
if err := i.store.BindTransaction(tname).Commit(context); err != nil {
- return verror2.Make(errOperationFailed, context.Context())
+ return verror.Make(ErrOperationFailed, context.Context())
}
return nil
}
@@ -209,9 +213,9 @@
return nil, nil
}
}
- return nil, verror2.Make(errNotFound, nil)
+ return nil, verror.Make(ErrNotFound, nil)
default:
- return nil, verror2.Make(errNotFound, nil)
+ return nil, verror.Make(ErrNotFound, nil)
}
ch := make(chan string, len(results))
@@ -221,3 +225,72 @@
close(ch)
return ch, nil
}
+
+func (i *appRepoService) GetACL(ctx ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
+ i.store.Lock()
+ defer i.store.Unlock()
+ path := naming.Join("/acls", i.suffix, "data")
+ return getACL(i.store, path)
+}
+
+func (i *appRepoService) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
+ i.store.Lock()
+ defer i.store.Unlock()
+ path := naming.Join("/acls", i.suffix, "data")
+ return setACL(i.store, path, acl, etag)
+}
+
+// getACL fetches a TaggedACLMap out of the Memstore at the provided path.
+// path is expected to already have been cleaned by naming.Join or its ilk.
+func getACL(store *fs.Memstore, path string) (access.TaggedACLMap, string, error) {
+ entry, err := store.BindObject(path).Get(nil)
+
+ if verror.Is(err, fs.ErrNotInMemStore.ID) {
+ // No ACL exists
+ return nil, "default", verror.Make(ErrNotFound, nil)
+ } else if err != nil {
+ vlog.Errorf("getACL: internal failure in fs.Memstore")
+ return nil, "", err
+ }
+
+ acl, ok := entry.Value.(access.TaggedACLMap)
+ if !ok {
+ return nil, "", err
+ }
+
+ etag, err := acls.ComputeEtag(acl)
+ if err != nil {
+ return nil, "", err
+ }
+ return acl, etag, nil
+}
+
+// setACL wites a TaggedACLMap into the Memstore at the provided path.
+// where path is expected to have already been cleaned by naming.Join.
+func setACL(store *fs.Memstore, path string, acl access.TaggedACLMap, etag string) error {
+ _, oetag, err := getACL(store, path)
+ if verror.Is(err, ErrNotFound.ID) {
+ oetag = etag
+ } else if err != nil {
+ return err
+ }
+
+ if oetag != etag {
+ return verror.Make(access.BadEtag, nil, etag, oetag)
+ }
+
+ tname, err := store.BindTransactionRoot("").CreateTransaction(nil)
+ if err != nil {
+ return err
+ }
+
+ object := store.BindObject(path)
+
+ if _, err := object.Put(nil, acl); err != nil {
+ return err
+ }
+ if err := store.BindTransaction(tname).Commit(nil); err != nil {
+ return verror.Make(ErrOperationFailed, nil)
+ }
+ return nil
+}
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index d521e5f..d5c7242 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -308,7 +308,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
// Current link does not have to live in the root dir, but it's
@@ -546,7 +546,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
// Create a script wrapping the test target that implements suidhelper.
@@ -767,7 +767,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "devicemanager")
@@ -839,7 +839,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
var (
@@ -944,7 +944,7 @@
func TestDeviceManagerInstallation(t *testing.T) {
sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
defer deferFn()
- testDir, cleanup := setupRootDir(t)
+ testDir, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
// Create a script wrapping the test target that implements suidhelper.
@@ -1002,7 +1002,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "devicemanager")
@@ -1168,7 +1168,7 @@
defer startRealBinaryRepository(t)()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
crDir, crEnv := mgmttest.CredentialsForChild(globalRT, "devicemanager")
@@ -1232,7 +1232,7 @@
sh, deferFn := mgmttest.CreateShellAndMountTable(t, globalRT)
defer deferFn()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
var (
@@ -1335,7 +1335,7 @@
envelope, cleanup := startMockRepos(t)
defer cleanup()
- root, cleanup := setupRootDir(t)
+ root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
var (
diff --git a/services/mgmt/device/impl/mock_repo_test.go b/services/mgmt/device/impl/mock_repo_test.go
index 56ae7be..82644a3 100644
--- a/services/mgmt/device/impl/mock_repo_test.go
+++ b/services/mgmt/device/impl/mock_repo_test.go
@@ -13,6 +13,7 @@
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/binary"
"v.io/core/veyron2/services/mgmt/repository"
+ "v.io/core/veyron2/services/security/access"
"v.io/core/veyron2/verror2"
"v.io/core/veyron2/vlog"
@@ -66,6 +67,14 @@
return i.envelope, nil
}
+func (i *arInvoker) GetACL(ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
+ return nil, "", nil
+}
+
+func (i *arInvoker) SetACL(_ ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
+ return nil
+}
+
// brInvoker holds the state of a binary repository invocation mock. It always
// serves the current running binary.
type brInvoker struct{}
diff --git a/services/mgmt/device/impl/util_test.go b/services/mgmt/device/impl/util_test.go
index 6cf2b0e..593b6e6 100644
--- a/services/mgmt/device/impl/util_test.go
+++ b/services/mgmt/device/impl/util_test.go
@@ -26,12 +26,6 @@
)
const (
- // Setting this environment variable to any non-empty value avoids
- // removing the device manager's workspace for successful test runs (for
- // failed test runs, this is already the case). This is useful when
- // developing test cases.
- preserveDMWorkspaceEnv = "VEYRON_TEST_PRESERVE_DM_WORKSPACE"
-
// TODO(caprita): Set the timeout in a more principled manner.
stopTimeout = 20 // In seconds.
)
@@ -48,29 +42,6 @@
}
}
-// setupRootDir sets up and returns the local filesystem location that the
-// device manager is told to use, as well as a cleanup function.
-func setupRootDir(t *testing.T) (string, func()) {
- rootDir, err := ioutil.TempDir("", "devicemanager")
- if err != nil {
- t.Fatalf("Failed to set up temporary dir for test: %v", err)
- }
- // On some operating systems (e.g. darwin) os.TempDir() can return a
- // symlink. To avoid having to account for this eventuality later,
- // evaluate the symlink.
- rootDir, err = filepath.EvalSymlinks(rootDir)
- if err != nil {
- vlog.Fatalf("EvalSymlinks(%v) failed: %v", rootDir, err)
- }
- return rootDir, func() {
- if t.Failed() || os.Getenv(preserveDMWorkspaceEnv) != "" {
- t.Logf("You can examine the device manager workspace at %v", rootDir)
- } else {
- os.RemoveAll(rootDir)
- }
- }
-}
-
// resolveExpectNotFound verifies that the given name is not in the mounttable.
func resolveExpectNotFound(t *testing.T, name string) {
if results, err := globalRT.Namespace().Resolve(globalRT.NewContext(), name); err == nil {
diff --git a/services/mgmt/lib/fs/simplestore.go b/services/mgmt/lib/fs/simplestore.go
index 9f8cd04..2f7abb2 100644
--- a/services/mgmt/lib/fs/simplestore.go
+++ b/services/mgmt/lib/fs/simplestore.go
@@ -11,7 +11,9 @@
"sync"
"v.io/core/veyron/services/mgmt/profile"
+
"v.io/core/veyron2/services/mgmt/application"
+ "v.io/core/veyron2/services/security/access"
verror "v.io/core/veyron2/verror2"
)
@@ -57,6 +59,7 @@
func init() {
gob.Register(profile.Specification{})
gob.Register(application.Envelope{})
+ gob.Register(access.TaggedACLMap{})
}
// NewMemstore persists the Memstore to os.TempDir() if no file is
@@ -335,7 +338,7 @@
return nil, verror.Make(ErrWithoutTransaction, nil, "Put()")
}
switch v := envelope.(type) {
- case application.Envelope, profile.Specification:
+ case application.Envelope, profile.Specification, access.TaggedACLMap:
o.ms.puts[o.path] = v
delete(o.ms.removes, o.path)
o.Value = o.path
diff --git a/services/mgmt/lib/testutil/modules.go b/services/mgmt/lib/testutil/modules.go
index ed6c087..d9ec550 100644
--- a/services/mgmt/lib/testutil/modules.go
+++ b/services/mgmt/lib/testutil/modules.go
@@ -1,7 +1,9 @@
package testutil
import (
+ "io/ioutil"
"os"
+ "path/filepath"
"strconv"
"testing"
@@ -18,6 +20,14 @@
"v.io/core/veyron/lib/testutil/security"
)
+const (
+ // Setting this environment variable to any non-empty value avoids
+ // removing the generated workspace for successful test runs (for
+ // failed test runs, this is already the case). This is useful when
+ // developing test cases.
+ preserveWorkspaceEnv = "VEYRON_TEST_PRESERVE_WORKSPACE"
+)
+
// StartRootMT sets up a root mount table for tests.
func StartRootMT(t *testing.T, sh *modules.Shell) (string, modules.Handle) {
h, err := sh.Start(core.RootMTCommand, nil, "--", "--veyron.tcp.address=127.0.0.1:0")
@@ -137,3 +147,27 @@
t.Fatalf(testutil.FormatLogLine(2, "failed to extract pid: %v", m))
return 0
}
+
+// SetupRootDir sets up and returns a directory for the root and returns
+// a cleanup function.
+func SetupRootDir(t *testing.T, prefix string) (string, func()) {
+ rootDir, err := ioutil.TempDir("", prefix)
+ if err != nil {
+ t.Fatalf("Failed to set up temporary dir for test: %v", err)
+ }
+ // On some operating systems (e.g. darwin) os.TempDir() can return a
+ // symlink. To avoid having to account for this eventuality later,
+ // evaluate the symlink.
+ rootDir, err = filepath.EvalSymlinks(rootDir)
+ if err != nil {
+ vlog.Fatalf("EvalSymlinks(%v) failed: %v", rootDir, err)
+ }
+
+ return rootDir, func() {
+ if t.Failed() || os.Getenv(preserveWorkspaceEnv) != "" {
+ t.Logf("You can examine the %s workspace at %v", prefix, rootDir)
+ } else {
+ os.RemoveAll(rootDir)
+ }
+ }
+}
diff --git a/services/mgmt/repository/repository.vdl.go b/services/mgmt/repository/repository.vdl.go
index b96abee..75842f2 100644
--- a/services/mgmt/repository/repository.vdl.go
+++ b/services/mgmt/repository/repository.vdl.go
@@ -207,7 +207,7 @@
}
func (s implApplicationServerStub) Describe__() []__ipc.InterfaceDesc {
- return []__ipc.InterfaceDesc{ApplicationDesc, repository.ApplicationDesc}
+ return []__ipc.InterfaceDesc{ApplicationDesc, repository.ApplicationDesc, access.ObjectDesc}
}
// ApplicationDesc describes the Application interface.