Merge "veyron/services/store/stored: Add viewer to stored itself (enabled by default), and update mdb example accordingly, with a proper Makefile and full usage instructions in a README.md file."
diff --git a/runtimes/google/rt/signal.go b/runtimes/google/rt/signal.go
index bbd5e55..d0ef7d4 100644
--- a/runtimes/google/rt/signal.go
+++ b/runtimes/google/rt/signal.go
@@ -8,6 +8,10 @@
)
func (r *vrt) initSignalHandling() {
+ // TODO(caprita): Given that our node manager implementation is to
+ // kill all child apps when the node manager dies, we should
+ // enable SIGHUP on apps by default.
+
// Automatically handle SIGHUP to prevent applications started as
// daemons from being killed. The developer can choose to still listen
// on SIGHUP and take a different action if desired.
diff --git a/runtimes/google/vsync/vsync.vdl.go b/runtimes/google/vsync/vsync.vdl.go
index 3b4602e..459e9da 100644
--- a/runtimes/google/vsync/vsync.vdl.go
+++ b/runtimes/google/vsync/vsync.vdl.go
@@ -342,36 +342,29 @@
{Name: "Err", Type: 68},
},
- OutStream: 82,
+ OutStream: 79,
}
result.TypeDefs = []_gen_vdlutil.Any{
- _gen_wiretype.NamedPrimitiveType{Type: 0x3, Name: "veyron/runtimes/google/vsync.DeviceID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.GenID", Tags: []string(nil)}, _gen_wiretype.MapType{Key: 0x41, Elem: 0x42, Name: "veyron/runtimes/google/vsync.GenVector", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.LSN", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.ArrayType{Elem: 0x46, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x48, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/storage.TagOp", Tags: []string(nil)}, _gen_wiretype.StructType{
- []_gen_wiretype.FieldType{
- _gen_wiretype.FieldType{Type: 0x4b, Name: "Op"},
- _gen_wiretype.FieldType{Type: 0x47, Name: "ACL"},
- },
- "veyron2/storage.Tag", []string(nil)},
- _gen_wiretype.SliceType{Elem: 0x4c, Name: "veyron2/storage.TagList", Tags: []string(nil)}, _gen_wiretype.StructType{
+ _gen_wiretype.NamedPrimitiveType{Type: 0x3, Name: "veyron/runtimes/google/vsync.DeviceID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.GenID", Tags: []string(nil)}, _gen_wiretype.MapType{Key: 0x41, Elem: 0x42, Name: "veyron/runtimes/google/vsync.GenVector", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron/runtimes/google/vsync.LSN", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.ArrayType{Elem: 0x46, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x48, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
_gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
_gen_wiretype.FieldType{Type: 0x47, Name: "ID"},
},
"veyron2/storage.DEntry", []string(nil)},
- _gen_wiretype.SliceType{Elem: 0x4e, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+ _gen_wiretype.SliceType{Elem: 0x4b, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
_gen_wiretype.FieldType{Type: 0x47, Name: "ID"},
_gen_wiretype.FieldType{Type: 0x48, Name: "PriorVersion"},
_gen_wiretype.FieldType{Type: 0x48, Name: "Version"},
_gen_wiretype.FieldType{Type: 0x2, Name: "IsRoot"},
_gen_wiretype.FieldType{Type: 0x4a, Name: "Value"},
- _gen_wiretype.FieldType{Type: 0x4d, Name: "Tags"},
- _gen_wiretype.FieldType{Type: 0x4f, Name: "Dir"},
+ _gen_wiretype.FieldType{Type: 0x4c, Name: "Dir"},
},
"veyron/services/store/raw.Mutation", []string(nil)},
_gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
- _gen_wiretype.FieldType{Type: 0x50, Name: "Mutation"},
+ _gen_wiretype.FieldType{Type: 0x4d, Name: "Mutation"},
_gen_wiretype.FieldType{Type: 0x25, Name: "SyncTime"},
_gen_wiretype.FieldType{Type: 0x2, Name: "Delete"},
_gen_wiretype.FieldType{Type: 0x2, Name: "Continued"},
@@ -386,7 +379,7 @@
_gen_wiretype.FieldType{Type: 0x47, Name: "ObjID"},
_gen_wiretype.FieldType{Type: 0x48, Name: "CurVers"},
_gen_wiretype.FieldType{Type: 0x49, Name: "Parents"},
- _gen_wiretype.FieldType{Type: 0x51, Name: "Value"},
+ _gen_wiretype.FieldType{Type: 0x4e, Name: "Value"},
},
"veyron/runtimes/google/vsync.LogRec", []string(nil)},
}
diff --git a/runtimes/google/vsync/watcher_test.go b/runtimes/google/vsync/watcher_test.go
index 64b5feb..dee40cf 100644
--- a/runtimes/google/vsync/watcher_test.go
+++ b/runtimes/google/vsync/watcher_test.go
@@ -166,7 +166,6 @@
PriorVersion: 0x0,
Version: 0x4d65822107fcfd52,
Value: "value-root",
- Tags: nil,
Dir: []storage.DEntry{
storage.DEntry{
Name: "a",
@@ -187,7 +186,6 @@
PriorVersion: 0x0,
Version: 0x57e9d1860d1d68d8,
Value: "value-a",
- Tags: nil,
Dir: []storage.DEntry{
storage.DEntry{
Name: "b",
@@ -208,7 +206,6 @@
PriorVersion: 0x0,
Version: 0x55104dc76695721d,
Value: "value-b",
- Tags: nil,
Dir: nil,
},
ResumeMarker: nil,
@@ -225,7 +222,6 @@
PriorVersion: 0x57e9d1860d1d68d8,
Version: 0x365a858149c6e2d1,
Value: "value-a",
- Tags: nil,
Dir: []storage.DEntry{
storage.DEntry{
Name: "b",
@@ -251,7 +247,6 @@
PriorVersion: 0x0,
Version: 0x380704bb7b4d7c03,
Value: "value-c",
- Tags: nil,
Dir: nil,
},
ResumeMarker: nil,
diff --git a/services/mgmt/lib/exec/doc.go b/services/mgmt/lib/exec/doc.go
index f0b8168..953965a 100644
--- a/services/mgmt/lib/exec/doc.go
+++ b/services/mgmt/lib/exec/doc.go
@@ -7,9 +7,7 @@
// for the child to terminate, and to terminate the child cleaning up any state
// associated with it.
//
-// A child process uses the NewChildHandle function to complete the initial
-// authentication handshake and must then call the Run() function to run a
-// goroutine to handle the process rendezvous. The child must call SetReady to
-// indicate that it is fully initialized and ready for whatever purpose it is
-// intended to fulfill.
+// A child process uses the GetChildHandle function to complete the initial
+// authentication handshake. The child must call SetReady to indicate that it is
+// fully initialized and ready for whatever purpose it is intended to fulfill.
package exec
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index 33abf91..c735952 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -2,7 +2,6 @@
import (
"fmt"
- "sync"
"veyron/services/mgmt/node"
"veyron/services/mgmt/node/config"
@@ -27,10 +26,8 @@
return &dispatcher{
auth: auth,
internal: &internalState{
- channels: make(map[string]chan string),
- channelsMutex: new(sync.Mutex),
- updating: false,
- updatingMutex: new(sync.Mutex),
+ channels: make(map[string]map[string]chan string),
+ updating: false,
},
config: config,
}, nil
@@ -39,6 +36,10 @@
// DISPATCHER INTERFACE IMPLEMENTATION
func (d *dispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
+ // TODO(caprita): Split out the logic that operates on the node manager
+ // from the logic that operates on the applications that the node
+ // manager runs. We can have different invoker implementations,
+ // dispatching based on the suffix ("nm" vs. "apps").
return ipc.ReflectInvoker(node.NewServerNode(&invoker{
internal: d.internal,
config: d.config,
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 79d6c2b..16988ec 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1,6 +1,8 @@
package impl_test
import (
+ "crypto/md5"
+ "encoding/base64"
"fmt"
"io/ioutil"
"os"
@@ -16,9 +18,11 @@
"veyron/services/mgmt/node/config"
"veyron/services/mgmt/node/impl"
+ "veyron2/ipc"
"veyron2/naming"
"veyron2/rt"
"veyron2/services/mgmt/application"
+ "veyron2/services/mgmt/node"
"veyron2/verror"
"veyron2/vlog"
)
@@ -33,8 +37,9 @@
// create it here.
rt.Init()
- blackbox.CommandTable["nodeManager"] = nodeManager
blackbox.CommandTable["execScript"] = execScript
+ blackbox.CommandTable["nodeManager"] = nodeManager
+ blackbox.CommandTable["app"] = app
}
// execScript launches the script passed as argument.
@@ -123,6 +128,38 @@
}
}
+// appService defines a test service that the test app should be running.
+// TODO(caprita): Use this to make calls to the app and verify how Suspend/Stop
+// interact with an active service.
+type appService struct{}
+
+func (appService) Echo(_ ipc.ServerCall, message string) (string, error) {
+ return message, nil
+}
+
+func app(args []string) {
+ if expected, got := 1, len(args); expected != got {
+ vlog.Fatalf("Unexpected number of arguments: expected %d, got %d", expected, got)
+ }
+ publishName := args[0]
+
+ defer rt.R().Cleanup()
+ server, _ := newServer()
+ defer server.Stop()
+ if err := server.Serve(publishName, ipc.SoloDispatcher(new(appService), nil)); err != nil {
+ vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
+ }
+ if call, err := rt.R().Client().StartCall(rt.R().NewContext(), "pingserver", "Ping", nil); err != nil {
+ vlog.Fatalf("StartCall failed: %v", err)
+ } else if err = call.Finish(); err != nil {
+ vlog.Fatalf("Finish failed: %v", err)
+ }
+ <-signals.ShutdownOnSignals()
+ if err := ioutil.WriteFile("testfile", []byte("goodbye world"), 0600); err != nil {
+ vlog.Fatalf("Failed to write testfile: %v", err)
+ }
+}
+
// generateScript is very similar in behavior to its namesake in invoker.go.
// However, we chose to re-implement it here for two reasons: (1) avoid making
// generateScript public; and (2) how the test choses to invoke the node manager
@@ -145,22 +182,28 @@
return path
}
+// nodeEnvelopeFromCmd returns a node manager application envelope that
+// describes the given command object.
+func nodeEnvelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
+ return envelopeFromCmd(application.NodeManagerTitle, cmd)
+}
+
// envelopeFromCmd returns an envelope that describes the given command object.
-func envelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
+func envelopeFromCmd(title string, cmd *goexec.Cmd) *application.Envelope {
return &application.Envelope{
- Title: application.NodeManagerTitle,
+ Title: title,
Args: cmd.Args[1:],
Env: cmd.Env,
Binary: "br",
}
}
-// TestUpdateAndRevert makes the node manager go through the motions of updating
+// TestNodeManagerUpdateAndRevert makes the node manager go through the motions of updating
// itself to newer versions (twice), and reverting itself back (twice). It also
// checks that update and revert fail when they're supposed to. The initial
// node manager is started 'by hand' via a blackbox command. Further versions
// are started through the soft link that the node manager itself updates.
-func TestUpdateAndRevert(t *testing.T) {
+func TestNodeManagerUpdateAndRevert(t *testing.T) {
// Set up mount table, application, and binary repositories.
defer setupLocalNamespace(t)()
envelope, cleanup := startApplicationRepository()
@@ -226,7 +269,7 @@
resolve(t, "factoryNM") // Verify the node manager has published itself.
// Simulate an invalid envelope in the application repository.
- *envelope = *envelopeFromCmd(nm.Cmd)
+ *envelope = *nodeEnvelopeFromCmd(nm.Cmd)
envelope.Title = "bogus"
updateExpectError(t, "factoryNM", verror.BadArg) // Incorrect title.
revertExpectError(t, "factoryNM", verror.NotFound) // No previous version available.
@@ -239,7 +282,7 @@
// node manager to stage the next version.
nmV2 := blackbox.HelperCommand(t, "nodeManager", "v2NM")
defer setupChildCommand(nmV2)()
- *envelope = *envelopeFromCmd(nmV2.Cmd)
+ *envelope = *nodeEnvelopeFromCmd(nmV2.Cmd)
update(t, "factoryNM")
// Current link should have been updated to point to v2.
@@ -288,7 +331,7 @@
// Create a third version of the node manager and issue an update.
nmV3 := blackbox.HelperCommand(t, "nodeManager", "v3NM")
defer setupChildCommand(nmV3)()
- *envelope = *envelopeFromCmd(nmV3.Cmd)
+ *envelope = *nodeEnvelopeFromCmd(nmV3.Cmd)
update(t, "v2NM")
scriptPathV3 := evalLink()
@@ -360,3 +403,104 @@
runNM.Expect("ready")
resolve(t, "factoryNM") // Current link should have been launching factory version.
}
+
+type pingServerDisp chan struct{}
+
+func (p pingServerDisp) Ping(ipc.ServerCall) { close(p) }
+
+// TestAppStartStop installs an app, starts it, and then stops it.
+func TestAppStartStop(t *testing.T) {
+ // Set up mount table, application, and binary repositories.
+ defer setupLocalNamespace(t)()
+ envelope, cleanup := startApplicationRepository()
+ defer cleanup()
+ defer startBinaryRepository()()
+
+ // This is the local filesystem location that the node manager is told
+ // to use.
+ root := filepath.Join(os.TempDir(), "nodemanager")
+ defer os.RemoveAll(root)
+
+ // Set up the node manager. Since we won't do node manager updates,
+ // don't worry about its application envelope and current link.
+ nm := blackbox.HelperCommand(t, "nodeManager", "nm", root, "unused app repo name", "unused curr link")
+ defer setupChildCommand(nm)()
+ if err := nm.Cmd.Start(); err != nil {
+ t.Fatalf("Start() failed: %v", err)
+ }
+ defer nm.Cleanup()
+ nm.Expect("ready")
+
+ // Create the local server that the app uses to let us know it's ready.
+ server, _ := newServer()
+ defer server.Stop()
+ pingCh := make(chan struct{})
+ if err := server.Serve("pingserver", ipc.SoloDispatcher(pingServerDisp(pingCh), nil)); err != nil {
+ t.Errorf("Failed to set up ping server")
+ }
+
+ // Create an envelope for an app.
+ app := blackbox.HelperCommand(t, "app", "app1")
+ defer setupChildCommand(app)()
+ appTitle := "google naps"
+ *envelope = *envelopeFromCmd(appTitle, app.Cmd)
+
+ appsName := "nm//apps"
+ stub, err := node.BindApplication(appsName)
+ if err != nil {
+ t.Fatalf("BindApplication(%v) failed: %v", appsName, err)
+ }
+ appID, err := stub.Install(rt.R().NewContext(), "ar")
+ if err != nil {
+ t.Fatalf("Install failed: %v", err)
+ }
+ appName := naming.Join(appsName, appID)
+ stub, err = node.BindApplication(appName)
+ if err != nil {
+ t.Fatalf("BindApplication(%v) failed: %v", appName, err)
+ }
+ var instanceID string
+ if instanceIDs, err := stub.Start(rt.R().NewContext()); err != nil {
+ t.Fatalf("Start failed: %v", err)
+ } else {
+ if want, got := 1, len(instanceIDs); want != got {
+ t.Fatalf("Expected %v instance ids, got %v instead", want, got)
+ }
+ instanceID = instanceIDs[0]
+ }
+ // Wait until the app pings us that it's ready.
+ <-pingCh
+
+ instanceName := naming.Join(appName, instanceID)
+ stub, err = node.BindApplication(instanceName)
+ if err != nil {
+ t.Fatalf("BindApplication(%v) failed: %v", instanceName, err)
+ }
+ if err := stub.Stop(rt.R().NewContext(), 5); err != nil {
+ t.Errorf("Stop failed: %v", err)
+ }
+
+ // HACK ALERT: for now, we peek inside the node manager's directory
+ // structure (which ought to be opaque) to check for what the app has
+ // written to its local root.
+ //
+ // TODO(caprita): add support to node manager to browse logs/app local
+ // root.
+ applicationDirName := func(title string) string {
+ h := md5.New()
+ h.Write([]byte(title))
+ hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+ return "app-" + hash
+ }
+ components := strings.Split(appID, "/")
+ appTitle, installationID := components[0], components[1]
+ instanceDir := filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "stopped-instance-"+instanceID)
+ rootDir := filepath.Join(instanceDir, "root")
+ testFile := filepath.Join(rootDir, "testfile")
+ if read, err := ioutil.ReadFile(testFile); err != nil {
+ t.Errorf("Failed to read %v: %v", testFile, err)
+ } else if want, got := "goodbye world", string(read); want != got {
+ t.Errorf("Expected to read %v, got %v instead", want, got)
+ }
+ // END HACK
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index b7b4611..4e95dff 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -3,14 +3,13 @@
// The implementation of the node manager expects that the node manager
// installations are all organized in the following directory structure:
//
-// VEYRON_NM_ROOT/
-// workspace-1/
-// noded - the node manager binary
-// noded.sh - a shell script to start the binary
-// ...
-// workspace-n/
-// noded - the node manager binary
-// noded.sh - a shell script to start the binary
+// <config.Root>/
+// node-manager/
+// <version 1 timestamp>/ - timestamp of when the version was downloaded
+// noded - the node manager binary
+// noded.sh - a shell script to start the binary
+// <version 2 timestamp>
+// ...
//
// The node manager is always expected to be started through the symbolic link
// passed in as config.CurrentLink, which is monitored by an init daemon. This
@@ -20,25 +19,88 @@
// the symlink is updated to the new noded.sh script. Similarly, to revert the
// node manager to a previous version, all that is required is to update the
// symlink to point to the previous noded.sh script.
+//
+//
+// The node manager manages the applications it installs and runs using the
+// following directory structure:
+//
+// TODO(caprita): Not all is yet implemented.
+//
+// <config.Root>/
+// app-<hash 1>/ - the application dir is named using a hash of the application title
+// installation-<id 1>/ - installations are labelled with ids
+// <version 1 timestamp>/ - timestamp of when the version was downloaded
+// bin - application binary
+// previous - symbolic link to previous version directory (TODO)
+// origin - object name for application envelope
+// envelope - application envelope (JSON-encoded)
+// <version 2 timestamp>
+// ...
+// current - symbolic link to the current version
+// instances/
+// instance-<id a>/ - instances are labelled with ids
+// root/ - workspace that the instance is run from
+// logs/ - stderr/stdout and log files generated by instance
+// info - app manager name and process id for the instance (if running)
+// version - symbolic link to installation version for the instance
+// instance-<id b>
+// ...
+// stopped-instance-<id c> - stopped instances have their directory name prepended by 'stopped-'
+// ...
+// installation-<id 2>
+// ...
+// app-<hash 2>
+// ...
+//
+// When node manager starts up, it goes through all instances and resumes the
+// ones that are not suspended. If the application was still running, it
+// suspends it first. If an application fails to resume, it stays suspended.
+//
+// When node manager shuts down, it suspends all running instances.
+//
+// Start starts an instance. Suspend kills the process but leaves the workspace
+// untouched. Resume restarts the process. Stop kills the process and prevents
+// future resumes (it also eventually gc's the workspace).
+//
+// If the process dies on its own, it stays dead and is assumed suspended.
+// TODO(caprita): Later, we'll add auto-restart option.
+//
+// Concurrency model: installations can be created independently of one another;
+// installations can be removed at any time (any running instances will be
+// stopped). The first call to Uninstall will rename the installation dir as a
+// first step; subsequent Uninstalls will fail. Instances can be created
+// independently of one another, as long as the installation exists (if it gets
+// Uninstalled during an instance Start, the Start may fail). When an instance
+// is stopped, the first call to Stop renames the instance dir; subsequent Stop
+// calls will fail. Resume will attempt to create an info file; if one exists
+// already, Resume fails. Suspend will attempt to rename the info file; if none
+// present, Suspend will fail.
+//
+// TODO(caprita): There is room for synergy between how node manager organizes
+// its own workspace and that for the applications it runs. In particular,
+// previous, origin, and envelope could be part of a single config. We'll
+// refine that later.
import (
- "bytes"
- "errors"
+ "crypto/md5"
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
"fmt"
+ "hash/crc64"
"io/ioutil"
- "math/rand"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
- "runtime"
+ "strconv"
"strings"
"sync"
"time"
"veyron/lib/config"
- blib "veyron/services/mgmt/lib/binary"
+ binlib "veyron/services/mgmt/lib/binary"
vexec "veyron/services/mgmt/lib/exec"
iconfig "veyron/services/mgmt/node/config"
"veyron/services/mgmt/profile"
@@ -47,30 +109,40 @@
"veyron2/mgmt"
"veyron2/naming"
"veyron2/rt"
+ "veyron2/services/mgmt/appcycle"
"veyron2/services/mgmt/application"
- "veyron2/services/mgmt/binary"
- "veyron2/services/mgmt/build"
+ binapi "veyron2/services/mgmt/binary"
"veyron2/services/mgmt/node"
"veyron2/services/mgmt/repository"
"veyron2/verror"
"veyron2/vlog"
)
+// instanceInfo holds state about a running instance.
+type instanceInfo struct {
+ AppCycleMgrName string
+ Pid int
+}
+
// internalState wraps state shared between different node manager
// invocations.
type internalState struct {
- // channels maps callback identifiers to channels that are used to
- // communicate information from child processes.
- channels map[string]chan string
+ // channels maps callback identifiers and config keys to channels that
+ // are used to communicate corresponding config values from child
+ // processes.
+ channels map[string]map[string]chan string
+ // nextCallbackID provides the next callback identifier to use as key
+ // for the channels map.
+ nextCallbackID int64
// channelsMutex is a lock for coordinating concurrent access to
// <channels>.
- channelsMutex *sync.Mutex
+ channelsMutex sync.Mutex
// updating is a flag that records whether this instance of node
// manager is being updated.
updating bool
// updatingMutex is a lock for coordinating concurrent access to
// <updating>.
- updatingMutex *sync.Mutex
+ updatingMutex sync.Mutex
}
// invoker holds the state of a node manager invocation.
@@ -94,217 +166,42 @@
errUpdateInProgress = verror.Existsf("update in progress")
errIncompatibleUpdate = verror.BadArgf("update failed: mismatching app title")
errUpdateNoOp = verror.NotFoundf("no different version available")
+ errNotExist = verror.NotFoundf("object does not exist")
+ errInvalidOperation = verror.BadArgf("invalid operation")
)
// NODE INTERFACE IMPLEMENTATION
-// computeNodeProfile generates a description of the runtime
-// environment (supported file format, OS, architecture, libraries) of
-// the host node.
-//
-// TODO(jsimsa): Avoid computing the host node description from
-// scratch if a recent cached copy exists.
-func (i *invoker) computeNodeProfile() (*profile.Specification, error) {
- result := profile.Specification{}
-
- // Find out what the supported file format, operating system, and
- // architecture is.
- switch runtime.GOOS {
- case "darwin":
- result.Format = build.MACH
- result.OS = build.Darwin
- case "linux":
- result.Format = build.ELF
- result.OS = build.Linux
- case "windows":
- result.Format = build.PE
- result.OS = build.Windows
- default:
- return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
- }
- switch runtime.GOARCH {
- case "amd64":
- result.Arch = build.AMD64
- case "arm":
- result.Arch = build.ARM
- case "x86":
- result.Arch = build.X86
- default:
- return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
- }
-
- // Find out what the installed dynamically linked libraries are.
- switch runtime.GOOS {
- case "linux":
- // For Linux, we identify what dynamically linked libraries are
- // install by parsing the output of "ldconfig -p".
- command := exec.Command("ldconfig", "-p")
- output, err := command.CombinedOutput()
- if err != nil {
- return nil, err
- }
- buf := bytes.NewBuffer(output)
- // Throw away the first line of output from ldconfig.
- if _, err := buf.ReadString('\n'); err != nil {
- return nil, errors.New("Could not identify libraries.")
- }
- // Extract the library name and version from every subsequent line.
- result.Libraries = make(map[profile.Library]struct{})
- line, err := buf.ReadString('\n')
- for err == nil {
- words := strings.Split(strings.Trim(line, " \t\n"), " ")
- if len(words) > 0 {
- tokens := strings.Split(words[0], ".so")
- if len(tokens) != 2 {
- return nil, errors.New("Could not identify library: " + words[0])
- }
- name := strings.TrimPrefix(tokens[0], "lib")
- major, minor := "", ""
- tokens = strings.SplitN(tokens[1], ".", 3)
- if len(tokens) >= 2 {
- major = tokens[1]
- }
- if len(tokens) >= 3 {
- minor = tokens[2]
- }
- result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
- }
- line, err = buf.ReadString('\n')
- }
- case "darwin":
- // TODO(jsimsa): Implement.
- case "windows":
- // TODO(jsimsa): Implement.
- default:
- return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
- }
- return &result, nil
-}
-
-// getProfile gets a profile description for the given profile.
-//
-// TODO(jsimsa): Avoid retrieving the list of known profiles from a
-// remote server if a recent cached copy exists.
-func (i *invoker) getProfile(name string) (*profile.Specification, error) {
- // TODO(jsimsa): This function assumes the existence of a profile
- // server from which the profiles can be retrieved. The profile
- // server is a work in progress. When it exists, the commented out
- // code below should work.
- var profile profile.Specification
- /*
- client, err := r.NewClient()
- if err != nil {
- vlog.Errorf("NewClient() failed: %v", err)
- return nil, err
- }
- defer client.Close()
- server := // TODO
- method := "Specification"
- inputs := make([]interface{}, 0)
- call, err := client.StartCall(server + "/" + name, method, inputs)
- if err != nil {
- vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
- return nil, err
- }
- if err := call.Finish(&profiles); err != nil {
- vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
- return nil, err
- }
- */
- return &profile, nil
-}
-
-// getKnownProfiles gets a list of description for all publicly known
-// profiles.
-//
-// TODO(jsimsa): Avoid retrieving the list of known profiles from a
-// remote server if a recent cached copy exists.
-func (i *invoker) getKnownProfiles() ([]profile.Specification, error) {
- // TODO(jsimsa): This function assumes the existence of a profile
- // server from which a list of known profiles can be retrieved. The
- // profile server is a work in progress. When it exists, the
- // commented out code below should work.
- knownProfiles := make([]profile.Specification, 0)
- /*
- client, err := r.NewClient()
- if err != nil {
- vlog.Errorf("NewClient() failed: %v\n", err)
- return nil, err
- }
- defer client.Close()
- server := // TODO
- method := "List"
- inputs := make([]interface{}, 0)
- call, err := client.StartCall(server, method, inputs)
- if err != nil {
- vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
- return nil, err
- }
- if err := call.Finish(&knownProfiles); err != nil {
- vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
- return nil, err
- }
- */
- return knownProfiles, nil
-}
-
-// matchProfiles inputs a profile that describes the host node and a
-// set of publicly known profiles and outputs a node description that
-// identifies the publicly known profiles supported by the host node.
-func (i *invoker) matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
- result := node.Description{Profiles: make(map[string]struct{})}
-loop:
- for _, profile := range known {
- if profile.Format != p.Format {
- continue
- }
- if profile.OS != p.OS {
- continue
- }
- if profile.Arch != p.Arch {
- continue
- }
- for library := range profile.Libraries {
- // Current implementation requires exact library name and version match.
- if _, found := p.Libraries[library]; !found {
- continue loop
- }
- }
- result.Profiles[profile.Label] = struct{}{}
- }
- return result
-}
-
func (i *invoker) Describe(call ipc.ServerContext) (node.Description, error) {
vlog.VI(1).Infof("%v.Describe()", i.suffix)
empty := node.Description{}
- nodeProfile, err := i.computeNodeProfile()
+ nodeProfile, err := computeNodeProfile()
if err != nil {
return empty, err
}
- knownProfiles, err := i.getKnownProfiles()
+ knownProfiles, err := getKnownProfiles()
if err != nil {
return empty, err
}
- result := i.matchProfiles(nodeProfile, knownProfiles)
+ result := matchProfiles(nodeProfile, knownProfiles)
return result, nil
}
-func (i *invoker) IsRunnable(call ipc.ServerContext, description binary.Description) (bool, error) {
+func (i *invoker) IsRunnable(call ipc.ServerContext, description binapi.Description) (bool, error) {
vlog.VI(1).Infof("%v.IsRunnable(%v)", i.suffix, description)
- nodeProfile, err := i.computeNodeProfile()
+ nodeProfile, err := computeNodeProfile()
if err != nil {
return false, err
}
binaryProfiles := make([]profile.Specification, 0)
for name, _ := range description.Profiles {
- profile, err := i.getProfile(name)
+ profile, err := getProfile(name)
if err != nil {
return false, err
}
binaryProfiles = append(binaryProfiles, *profile)
}
- result := i.matchProfiles(nodeProfile, binaryProfiles)
+ result := matchProfiles(nodeProfile, binaryProfiles)
return len(result.Profiles) > 0, nil
}
@@ -316,13 +213,13 @@
// APPLICATION INTERFACE IMPLEMENTATION
-func downloadBinary(workspace, name string) error {
- data, err := blib.Download(name)
+func downloadBinary(workspace, fileName, name string) error {
+ data, err := binlib.Download(name)
if err != nil {
vlog.Errorf("Download(%v) failed: %v", name, err)
return errOperationFailed
}
- path, perm := filepath.Join(workspace, "noded"), os.FileMode(755)
+ path, perm := filepath.Join(workspace, fileName), os.FileMode(755)
if err := ioutil.WriteFile(path, data, perm); err != nil {
vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
return errOperationFailed
@@ -347,13 +244,13 @@
return &envelope, nil
}
-func generateBinary(workspace string, envelope *application.Envelope, newBinary bool) error {
+func generateBinary(workspace, fileName string, envelope *application.Envelope, newBinary bool) error {
if newBinary {
// Download the new binary.
- return downloadBinary(workspace, envelope.Binary)
+ return downloadBinary(workspace, fileName, envelope.Binary)
}
// Link the current binary.
- path := filepath.Join(workspace, "noded")
+ path := filepath.Join(workspace, fileName)
if err := os.Link(os.Args[0], path); err != nil {
vlog.Errorf("Link(%v, %v) failed: %v", os.Args[0], path, err)
return errOperationFailed
@@ -374,7 +271,7 @@
output += filepath.Join(workspace, "noded") + " "
output += strings.Join(envelope.Args, " ")
path = filepath.Join(workspace, "noded.sh")
- if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
+ if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
vlog.Errorf("WriteFile(%v) failed: %v", path, err)
return errOperationFailed
}
@@ -419,10 +316,20 @@
return nil
}
-func (i *invoker) registerCallback(id string, channel chan string) {
+func (i *invoker) generateCallbackID() string {
i.internal.channelsMutex.Lock()
defer i.internal.channelsMutex.Unlock()
- i.internal.channels[id] = channel
+ i.internal.nextCallbackID++
+ return strconv.FormatInt(i.internal.nextCallbackID-1, 10)
+}
+
+func (i *invoker) registerCallback(id, key string, channel chan string) {
+ i.internal.channelsMutex.Lock()
+ defer i.internal.channelsMutex.Unlock()
+ if _, ok := i.internal.channels[id]; !ok {
+ i.internal.channels[id] = make(map[string]chan string)
+ }
+ i.internal.channels[id][key] = channel
}
func (i *invoker) revertNodeManager() error {
@@ -439,25 +346,30 @@
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Setup up the child process callback.
- id := fmt.Sprintf("%d", rand.Int())
+ id := i.generateCallbackID()
cfg := config.New()
cfg.Set(mgmt.ParentNodeManagerConfigKey, naming.MakeTerminal(naming.Join(i.config.Name, id)))
handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
- callbackChan := make(chan string)
- i.registerCallback(id, callbackChan)
- defer i.unregisterCallback(id)
+ // Make the channel buffered to avoid blocking the Set method when
+ // nothing is receiving on the channel. This happens e.g. when
+ // unregisterCallbacks executes before Set is called.
+ callbackChan := make(chan string, 1)
+ i.registerCallback(id, mgmt.ChildNodeManagerConfigKey, callbackChan)
+ defer i.unregisterCallbacks(id)
// Start the child process.
if err := handle.Start(); err != nil {
vlog.Errorf("Start() failed: %v", err)
return errOperationFailed
}
+ defer func() {
+ if err := handle.Clean(); err != nil {
+ vlog.Errorf("Clean() failed: %v", err)
+ }
+ }()
// Wait for the child process to start.
testTimeout := 10 * time.Second
if err := handle.WaitForReady(testTimeout); err != nil {
vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
- if err := cmd.Process.Kill(); err != nil {
- vlog.Errorf("Kill() failed: %v", err)
- }
return errOperationFailed
}
// Wait for the child process to invoke the Callback().
@@ -468,16 +380,10 @@
nmClient, err := node.BindNode(childName)
if err != nil {
vlog.Errorf("BindNode(%v) failed: %v", childName, err)
- if err := handle.Clean(); err != nil {
- vlog.Errorf("Clean() failed: %v", err)
- }
return errOperationFailed
}
linkOld, pathOld, err := i.getCurrentFileInfo()
if err != nil {
- if err := handle.Clean(); err != nil {
- vlog.Errorf("Clean() failed: %v", err)
- }
return errOperationFailed
}
// Since the resolution of mtime for files is seconds,
@@ -485,16 +391,10 @@
// check whether the current symlink is updated.
time.Sleep(time.Second)
if err := nmClient.Revert(rt.R().NewContext()); err != nil {
- if err := handle.Clean(); err != nil {
- vlog.Errorf("Clean() failed: %v", err)
- }
return errOperationFailed
}
linkNew, pathNew, err := i.getCurrentFileInfo()
if err != nil {
- if err := handle.Clean(); err != nil {
- vlog.Errorf("Clean() failed: %v", err)
- }
return errOperationFailed
}
// Check that the new node manager updated the current symbolic
@@ -512,15 +412,12 @@
}
case <-time.After(testTimeout):
vlog.Errorf("Waiting for callback timed out")
- if err := handle.Clean(); err != nil {
- vlog.Errorf("Clean() failed: %v", err)
- }
return errOperationFailed
}
return nil
}
-func (i *invoker) unregisterCallback(id string) {
+func (i *invoker) unregisterCallbacks(id string) {
i.internal.channelsMutex.Lock()
defer i.internal.channelsMutex.Unlock()
delete(i.internal.channels, id)
@@ -541,57 +438,175 @@
return errUpdateNoOp
}
// Create new workspace.
- workspace := filepath.Join(i.config.Root, fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano)))
- perm := os.FileMode(0755)
+ workspace := filepath.Join(i.config.Root, "node-manager", generateVersionDirName())
+ perm := os.FileMode(0700)
if err := os.MkdirAll(workspace, perm); err != nil {
vlog.Errorf("MkdirAll(%v, %v) failed: %v", workspace, perm, err)
return errOperationFailed
}
+ deferrer := func() {
+ if err := os.RemoveAll(workspace); err != nil {
+ vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
+ }
+ }
+ defer func() {
+ if deferrer != nil {
+ deferrer()
+ }
+ }()
// Populate the new workspace with a node manager binary.
// TODO(caprita): match identical binaries on binary metadata
// rather than binary object name.
sameBinary := i.config.Envelope != nil && envelope.Binary == i.config.Envelope.Binary
- if err := generateBinary(workspace, envelope, !sameBinary); err != nil {
- if err := os.RemoveAll(workspace); err != nil {
- vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
- }
+ if err := generateBinary(workspace, "noded", envelope, !sameBinary); err != nil {
return err
}
// Populate the new workspace with a node manager script.
configSettings, err := i.config.Save(envelope)
if err != nil {
- if err := os.RemoveAll(workspace); err != nil {
- vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
- }
return errOperationFailed
}
if err := generateScript(workspace, configSettings, envelope); err != nil {
- if err := os.RemoveAll(workspace); err != nil {
- vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
- }
return err
}
if err := i.testNodeManager(workspace, envelope); err != nil {
- if err := os.RemoveAll(workspace); err != nil {
- vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
- }
return err
}
// If the binary has changed, update the node manager symlink.
if err := i.updateLink(filepath.Join(workspace, "noded.sh")); err != nil {
- if err := os.RemoveAll(workspace); err != nil {
- vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
- }
return err
}
rt.R().Stop()
+ deferrer = nil
return nil
}
-func (i *invoker) Install(call ipc.ServerContext, von string) (string, error) {
- vlog.VI(1).Infof("%v.Install(%q)", i.suffix, von)
- // TODO(jsimsa): Implement.
- return "", nil
+func saveEnvelope(dir string, envelope *application.Envelope) error {
+ jsonEnvelope, err := json.Marshal(envelope)
+ if err != nil {
+ vlog.Errorf("Marshal(%v) failed: %v", envelope, err)
+ return errOperationFailed
+ }
+ envelopePath := filepath.Join(dir, "envelope")
+ if err := ioutil.WriteFile(envelopePath, jsonEnvelope, 0600); err != nil {
+ vlog.Errorf("WriteFile(%v) failed: %v", envelopePath, err)
+ return errOperationFailed
+ }
+ return nil
+}
+
+func loadEnvelope(dir string) (*application.Envelope, error) {
+ envelopePath := filepath.Join(dir, "envelope")
+ envelope := new(application.Envelope)
+ if envelopeBytes, err := ioutil.ReadFile(envelopePath); err != nil {
+ vlog.Errorf("ReadFile(%v) failed: %v", envelopePath, err)
+ return nil, errOperationFailed
+ } else if err := json.Unmarshal(envelopeBytes, envelope); err != nil {
+ vlog.Errorf("Unmarshal(%v) failed: %v", envelopeBytes, err)
+ return nil, errOperationFailed
+ }
+ return envelope, nil
+}
+
+func saveOrigin(dir, originVON string) error {
+ path := filepath.Join(dir, "origin")
+ if err := ioutil.WriteFile(path, []byte(originVON), 0600); err != nil {
+ vlog.Errorf("WriteFile(%v) failed: %v", path, err)
+ return errOperationFailed
+ }
+ return nil
+}
+
+// generateID returns a new unique id string. The uniqueness is based on the
+// current timestamp. Not cryptographically secure.
+func generateID() string {
+ timestamp := fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano))
+ h := crc64.New(crc64.MakeTable(crc64.ISO))
+ h.Write([]byte(timestamp))
+ b := make([]byte, 8)
+ binary.LittleEndian.PutUint64(b, uint64(h.Sum64()))
+ return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
+}
+
+// TODO(caprita): Nothing prevents different applications from sharing the same
+// title, and thereby being installed in the same app dir. Do we want to
+// prevent that for the same user or across users?
+
+// applicationDirName generates a cryptographic hash of the application title,
+// to be used as a directory name for installations of the application with the
+// given title.
+func applicationDirName(title string) string {
+ h := md5.New()
+ h.Write([]byte(title))
+ hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+ return "app-" + hash
+}
+
+func installationDirName(installationID string) string {
+ return "installation-" + installationID
+}
+
+func instanceDirName(instanceID string) string {
+ return "instance-" + instanceID
+}
+
+func stoppedInstanceDirName(instanceID string) string {
+ return "stopped-instance-" + instanceID
+}
+
+func generateVersionDirName() string {
+ return time.Now().Format(time.RFC3339Nano)
+}
+
+func (i *invoker) Install(call ipc.ServerContext, applicationVON string) (string, error) {
+ vlog.VI(1).Infof("%v.Install(%q)", i.suffix, applicationVON)
+ if i.suffix != "apps" {
+ return "", errInvalidSuffix
+ }
+ envelope, err := fetchEnvelope(applicationVON)
+ if err != nil {
+ return "", err
+ }
+ if envelope.Title == application.NodeManagerTitle {
+ // Disallow node manager apps from being installed like a
+ // regular app.
+ return "", errInvalidOperation
+ }
+ installationID := generateID()
+ installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
+ versionDir := filepath.Join(installationDir, generateVersionDirName())
+ perm := os.FileMode(0700)
+ if err := os.MkdirAll(versionDir, perm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", versionDir, perm, err)
+ return "", errOperationFailed
+ }
+ deferrer := func() {
+ if err := os.RemoveAll(versionDir); err != nil {
+ vlog.Errorf("RemoveAll(%v) failed: %v", versionDir, err)
+ }
+ }
+ defer func() {
+ if deferrer != nil {
+ deferrer()
+ }
+ }()
+ // TODO(caprita): Share binaries if already existing locally.
+ if err := generateBinary(versionDir, "bin", envelope, true); err != nil {
+ return "", err
+ }
+ if err := saveEnvelope(versionDir, envelope); err != nil {
+ return "", err
+ }
+ if err := saveOrigin(versionDir, applicationVON); err != nil {
+ return "", err
+ }
+ link := filepath.Join(installationDir, "current")
+ if err := os.Symlink(versionDir, link); err != nil {
+ vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, link, err)
+ return "", errOperationFailed
+ }
+ deferrer = nil
+ return naming.Join(envelope.Title, installationID), nil
}
func (i *invoker) Refresh(call ipc.ServerContext) error {
@@ -634,15 +649,221 @@
return err
}
-func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
- vlog.VI(1).Infof("%v.Start()", i.suffix)
- // TODO(jsimsa): Implement.
- return make([]string, 0), nil
+func splitName(name string) (ret []string) {
+ components := strings.Split(name, "/")
+ for _, c := range components {
+ if len(c) > 0 {
+ ret = append(ret, c)
+ }
+ }
+ return
}
-func (i *invoker) Stop(call ipc.ServerContext, deadline uint64) error {
+func generateCommand(envelope *application.Envelope, binPath, instanceDir string) (*exec.Cmd, error) {
+ // TODO(caprita): For the purpose of isolating apps, we should run them
+ // as different users. We'll need to either use the root process or a
+ // suid script to be able to do it.
+ cmd := exec.Command(binPath)
+ // TODO(caprita): Also pass in configuration info like NAMESPACE_ROOT to
+ // the app (to point to the device mounttable).
+ cmd.Env = envelope.Env
+ rootDir := filepath.Join(instanceDir, "root")
+ perm := os.FileMode(0700)
+ if err := os.MkdirAll(rootDir, perm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", rootDir, perm, err)
+ return nil, err
+ }
+ cmd.Dir = rootDir
+ logDir := filepath.Join(instanceDir, "logs")
+ if err := os.MkdirAll(logDir, perm); err != nil {
+ vlog.Errorf("MkdirAll(%v, %v) failed: %v", logDir, perm, err)
+ return nil, err
+ }
+ timestamp := time.Now().UnixNano()
+ var err error
+ perm = os.FileMode(0600)
+ cmd.Stdout, err = os.OpenFile(filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp)), os.O_WRONLY|os.O_CREATE, perm)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd.Stderr, err = os.OpenFile(filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp)), os.O_WRONLY|os.O_CREATE, perm)
+ if err != nil {
+ return nil, err
+ }
+ // Set up args and env.
+ cmd.Args = append(cmd.Args, "--log_dir=../logs")
+ cmd.Args = append(cmd.Args, envelope.Args...)
+ return cmd, nil
+}
+
+func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
+ vlog.VI(1).Infof("%v.Start()", i.suffix)
+ if !strings.HasPrefix(i.suffix, "apps") {
+ return nil, errInvalidSuffix
+ }
+ components := splitName(strings.TrimPrefix(i.suffix, "apps"))
+ if nComponents := len(components); nComponents < 2 {
+ return nil, fmt.Errorf("Start all installations / all applications not yet implemented (%v)", i.suffix)
+ } else if nComponents > 2 {
+ return nil, errInvalidSuffix
+ }
+ app, installation := components[0], components[1]
+ installationDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation))
+ if _, err := os.Stat(installationDir); err != nil {
+ if os.IsNotExist(err) {
+ return nil, errNotExist
+ }
+ vlog.Errorf("Stat(%v) failed: %v", installationDir, err)
+ return nil, errOperationFailed
+ }
+ currLink := filepath.Join(installationDir, "current")
+ envelope, err := loadEnvelope(currLink)
+ if err != nil {
+ return nil, err
+ }
+ binPath := filepath.Join(currLink, "bin")
+ if _, err := os.Stat(binPath); err != nil {
+ vlog.Errorf("Stat(%v) failed: %v", binPath, err)
+ return nil, errOperationFailed
+ }
+ instanceID := generateID()
+ // TODO(caprita): Clean up instanceDir upon failure.
+ instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
+ cmd, err := generateCommand(envelope, binPath, instanceDir)
+ if err != nil {
+ vlog.Errorf("generateCommand(%v, %v, %v) failed: %v", envelope, binPath, instanceDir, err)
+ return nil, errOperationFailed
+ }
+ // Setup up the child process callback.
+ id := i.generateCallbackID()
+ cfg := config.New()
+ cfg.Set(mgmt.ParentNodeManagerConfigKey, naming.MakeTerminal(naming.Join(i.config.Name, id)))
+ handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
+ // Make the channel buffered to avoid blocking the Set method when
+ // nothing is receiving on the channel. This happens e.g. when
+ // unregisterCallbacks executes before Set is called.
+ callbackChan := make(chan string, 1)
+ i.registerCallback(id, mgmt.AppCycleManagerConfigKey, callbackChan)
+ defer i.unregisterCallbacks(id)
+ // Start the child process.
+ if err := handle.Start(); err != nil {
+ vlog.Errorf("Start() failed: %v", err)
+ return nil, errOperationFailed
+ }
+ // Wait for the child process to start.
+ testTimeout := 10 * time.Second
+ if err := handle.WaitForReady(testTimeout); err != nil {
+ vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
+ if err := handle.Clean(); err != nil {
+ vlog.Errorf("Clean() failed: %v", err)
+ }
+ return nil, errOperationFailed
+ }
+ select {
+ case childName := <-callbackChan:
+ instanceInfo := &instanceInfo{
+ AppCycleMgrName: childName,
+ Pid: handle.Pid(),
+ }
+ if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
+ if err := handle.Clean(); err != nil {
+ vlog.Errorf("Clean() failed: %v", err)
+ }
+ return nil, err
+ }
+ // TODO(caprita): Spin up a goroutine to reap child status upon
+ // exit and transition it to suspended state if it exits on its
+ // own.
+ case <-time.After(testTimeout):
+ vlog.Errorf("Waiting for callback timed out")
+ if err := handle.Clean(); err != nil {
+ vlog.Errorf("Clean() failed: %v", err)
+ }
+ return nil, errOperationFailed
+ }
+ return []string{instanceID}, nil
+}
+
+func saveInstanceInfo(dir string, info *instanceInfo) error {
+ jsonInfo, err := json.Marshal(info)
+ if err != nil {
+ vlog.Errorf("Marshal(%v) failed: %v", info, err)
+ return errOperationFailed
+ }
+ infoPath := filepath.Join(dir, "info")
+ if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
+ vlog.Errorf("WriteFile(%v) failed: %v", infoPath, err)
+ return errOperationFailed
+ }
+ return nil
+}
+
+func loadInstanceInfo(dir string) (*instanceInfo, error) {
+ infoPath := filepath.Join(dir, "info")
+ info := new(instanceInfo)
+ if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
+ vlog.Errorf("ReadFile(%v) failed: %v", infoPath, err)
+ return nil, errOperationFailed
+ } else if err := json.Unmarshal(infoBytes, info); err != nil {
+ vlog.Errorf("Unmarshal(%v) failed: %v", infoBytes, err)
+ return nil, errOperationFailed
+ }
+ return info, nil
+}
+
+func (i *invoker) Stop(call ipc.ServerContext, deadline uint32) error {
+ // TODO(caprita): implement deadline.
vlog.VI(1).Infof("%v.Stop(%v)", i.suffix, deadline)
- // TODO(jsimsa): Implement.
+ if !strings.HasPrefix(i.suffix, "apps") {
+ return errInvalidSuffix
+ }
+ components := splitName(strings.TrimPrefix(i.suffix, "apps"))
+ if nComponents := len(components); nComponents < 3 {
+ return fmt.Errorf("Stop all instances / all installations / all applications not yet implemented (%v)", i.suffix)
+ } else if nComponents > 3 {
+ return errInvalidSuffix
+ }
+ app, installation, instance := components[0], components[1], components[2]
+ instancesDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation), "instances")
+ instanceDir := filepath.Join(instancesDir, instanceDirName(instance))
+ stoppedInstanceDir := filepath.Join(instancesDir, stoppedInstanceDirName(instance))
+ if err := os.Rename(instanceDir, stoppedInstanceDir); err != nil {
+ vlog.Errorf("Rename(%v, %v) failed: %v", instanceDir, stoppedInstanceDir, err)
+ if os.IsNotExist(err) {
+ return errNotExist
+ }
+ vlog.Errorf("Rename(%v, %v) failed: %v", instanceDir, stoppedInstanceDir, err)
+ return errOperationFailed
+ }
+ // TODO(caprita): restore the instance to unstopped upon failure?
+
+ info, err := loadInstanceInfo(stoppedInstanceDir)
+ if err != nil {
+ return errOperationFailed
+ }
+ appStub, err := appcycle.BindAppCycle(info.AppCycleMgrName)
+ if err != nil {
+ vlog.Errorf("BindAppCycle(%v) failed: %v", info.AppCycleMgrName, err)
+ return errOperationFailed
+ }
+ stream, err := appStub.Stop(rt.R().NewContext())
+ if err != nil {
+ vlog.Errorf("Got error: %v", err)
+ return errOperationFailed
+ }
+ rstream := stream.RecvStream()
+ for rstream.Advance() {
+ vlog.VI(2).Infof("%v.Stop(%v) task update: %v", i.suffix, deadline, rstream.Value())
+ }
+ if err := rstream.Err(); err != nil {
+ vlog.Errorf("Stream returned an error: %v", err)
+ return errOperationFailed
+ }
+ if err := stream.Finish(); err != nil {
+ vlog.Errorf("Got error: %v", err)
+ return errOperationFailed
+ }
return nil
}
@@ -697,16 +918,16 @@
func (i *invoker) Set(_ ipc.ServerContext, key, value string) error {
vlog.VI(1).Infof("%v.Set(%v, %v)", i.suffix, key, value)
- // For now, only handle the child node manager name. We'll add handling
- // for the child's app cycle manager name later on.
- if key != mgmt.ChildNodeManagerConfigKey {
- return nil
- }
+ id := i.suffix
i.internal.channelsMutex.Lock()
- channel, ok := i.internal.channels[i.suffix]
+ if _, ok := i.internal.channels[id]; !ok {
+ i.internal.channelsMutex.Unlock()
+ return errInvalidSuffix
+ }
+ channel, ok := i.internal.channels[id][key]
i.internal.channelsMutex.Unlock()
if !ok {
- return errInvalidSuffix
+ return nil
}
channel <- value
return nil
diff --git a/services/mgmt/node/impl/profile.go b/services/mgmt/node/impl/profile.go
new file mode 100644
index 0000000..d522784
--- /dev/null
+++ b/services/mgmt/node/impl/profile.go
@@ -0,0 +1,191 @@
+package impl
+
+import (
+ "bytes"
+ "errors"
+ "os/exec"
+ "runtime"
+ "strings"
+
+ "veyron/services/mgmt/profile"
+
+ "veyron2/services/mgmt/build"
+ "veyron2/services/mgmt/node"
+)
+
+// computeNodeProfile generates a description of the runtime
+// environment (supported file format, OS, architecture, libraries) of
+// the host node.
+//
+// TODO(jsimsa): Avoid computing the host node description from
+// scratch if a recent cached copy exists.
+func computeNodeProfile() (*profile.Specification, error) {
+ result := profile.Specification{}
+
+ // Find out what the supported file format, operating system, and
+ // architecture is.
+ switch runtime.GOOS {
+ case "darwin":
+ result.Format = build.MACH
+ result.OS = build.Darwin
+ case "linux":
+ result.Format = build.ELF
+ result.OS = build.Linux
+ case "windows":
+ result.Format = build.PE
+ result.OS = build.Windows
+ default:
+ return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
+ }
+ switch runtime.GOARCH {
+ case "amd64":
+ result.Arch = build.AMD64
+ case "arm":
+ result.Arch = build.ARM
+ case "x86":
+ result.Arch = build.X86
+ default:
+ return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
+ }
+
+ // Find out what the installed dynamically linked libraries are.
+ switch runtime.GOOS {
+ case "linux":
+ // For Linux, we identify what dynamically linked libraries are
+ // install by parsing the output of "ldconfig -p".
+ command := exec.Command("ldconfig", "-p")
+ output, err := command.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+ buf := bytes.NewBuffer(output)
+ // Throw away the first line of output from ldconfig.
+ if _, err := buf.ReadString('\n'); err != nil {
+ return nil, errors.New("Could not identify libraries.")
+ }
+ // Extract the library name and version from every subsequent line.
+ result.Libraries = make(map[profile.Library]struct{})
+ line, err := buf.ReadString('\n')
+ for err == nil {
+ words := strings.Split(strings.Trim(line, " \t\n"), " ")
+ if len(words) > 0 {
+ tokens := strings.Split(words[0], ".so")
+ if len(tokens) != 2 {
+ return nil, errors.New("Could not identify library: " + words[0])
+ }
+ name := strings.TrimPrefix(tokens[0], "lib")
+ major, minor := "", ""
+ tokens = strings.SplitN(tokens[1], ".", 3)
+ if len(tokens) >= 2 {
+ major = tokens[1]
+ }
+ if len(tokens) >= 3 {
+ minor = tokens[2]
+ }
+ result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
+ }
+ line, err = buf.ReadString('\n')
+ }
+ case "darwin":
+ // TODO(jsimsa): Implement.
+ case "windows":
+ // TODO(jsimsa): Implement.
+ default:
+ return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
+ }
+ return &result, nil
+}
+
+// getProfile gets a profile description for the given profile.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func getProfile(name string) (*profile.Specification, error) {
+ // TODO(jsimsa): This function assumes the existence of a profile
+ // server from which the profiles can be retrieved. The profile
+ // server is a work in progress. When it exists, the commented out
+ // code below should work.
+ var profile profile.Specification
+ /*
+ client, err := r.NewClient()
+ if err != nil {
+ vlog.Errorf("NewClient() failed: %v", err)
+ return nil, err
+ }
+ defer client.Close()
+ server := // TODO
+ method := "Specification"
+ inputs := make([]interface{}, 0)
+ call, err := client.StartCall(server + "/" + name, method, inputs)
+ if err != nil {
+ vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
+ return nil, err
+ }
+ if err := call.Finish(&profiles); err != nil {
+ vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
+ return nil, err
+ }
+ */
+ return &profile, nil
+}
+
+// getKnownProfiles gets a list of description for all publicly known
+// profiles.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func getKnownProfiles() ([]profile.Specification, error) {
+ // TODO(jsimsa): This function assumes the existence of a profile
+ // server from which a list of known profiles can be retrieved. The
+ // profile server is a work in progress. When it exists, the
+ // commented out code below should work.
+ knownProfiles := make([]profile.Specification, 0)
+ /*
+ client, err := r.NewClient()
+ if err != nil {
+ vlog.Errorf("NewClient() failed: %v\n", err)
+ return nil, err
+ }
+ defer client.Close()
+ server := // TODO
+ method := "List"
+ inputs := make([]interface{}, 0)
+ call, err := client.StartCall(server, method, inputs)
+ if err != nil {
+ vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
+ return nil, err
+ }
+ if err := call.Finish(&knownProfiles); err != nil {
+ vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
+ return nil, err
+ }
+ */
+ return knownProfiles, nil
+}
+
+// matchProfiles inputs a profile that describes the host node and a
+// set of publicly known profiles and outputs a node description that
+// identifies the publicly known profiles supported by the host node.
+func matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
+ result := node.Description{Profiles: make(map[string]struct{})}
+loop:
+ for _, profile := range known {
+ if profile.Format != p.Format {
+ continue
+ }
+ if profile.OS != p.OS {
+ continue
+ }
+ if profile.Arch != p.Arch {
+ continue
+ }
+ for library := range profile.Libraries {
+ // Current implementation requires exact library name and version match.
+ if _, found := p.Libraries[library]; !found {
+ continue loop
+ }
+ }
+ result.Profiles[profile.Label] = struct{}{}
+ }
+ return result
+}
diff --git a/services/store/memstore/acl/cache.go b/services/store/memstore/acl/cache.go
deleted file mode 100644
index cd3f5b9..0000000
--- a/services/store/memstore/acl/cache.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package acl
-
-import (
- "bytes"
-
- "veyron/runtimes/google/lib/functional"
- "veyron/runtimes/google/lib/functional/rb"
- "veyron2/storage"
-)
-
-// FindFunc is used to fetch the ACL given its ID.
-type FindFunc func(id storage.ID) *storage.ACL
-
-// Cache keeps a map from ID -> ACL, based on the contents of the store.
-//
-// Cache is thread-compatible -- mutating methods require the caller to
-// have exclusive access to the object.
-//
-// TODO(jyh): Currently, the cache can grow without bound. Implement a
-// replacement policy, for a cache with a bounded number of entries.
-type Cache struct {
- contents functional.Set // *cacheEntry
- find FindFunc
-}
-
-// cacheEntry is an entry in the cache, mapping storage.ID -> storage.ACL.
-type cacheEntry struct {
- id storage.ID
- acl *storage.ACL
-}
-
-var (
- emptyCacheContents = rb.NewSet(compareCacheEntriesByID)
-)
-
-// compareCacheEntriesByID compares the two cells' IDs.
-func compareCacheEntriesByID(a, b interface{}) bool {
- return bytes.Compare(a.(*cacheEntry).id[:], b.(*cacheEntry).id[:]) < 0
-}
-
-// NewCache returns an empty cache.
-func NewCache(find FindFunc) Cache {
- return Cache{contents: emptyCacheContents, find: find}
-}
-
-// Clear resets the cache.
-func (c *Cache) Clear() {
- c.contents = emptyCacheContents
- c.find = nil
-}
-
-// Invalidate removes cache entry. It should be called whenever the ACL
-// associated with the ID is changed.
-func (c *Cache) Invalidate(id storage.ID) {
- c.contents = c.contents.Remove(&cacheEntry{id: id})
-}
-
-// UpdateFind changes the find function.
-func (c *Cache) UpdateFinder(find FindFunc) {
- c.find = find
-}
-
-// get fetches a cache entry. If the entry is not in the cache, the function
-// <find> is used to fetch the value, the result is saved in the cache, and
-// returned.
-func (c *Cache) get(id storage.ID) *storage.ACL {
- e := &cacheEntry{id: id}
- x, ok := c.contents.Get(e)
- if ok {
- return x.(*cacheEntry).acl
- }
- acl := c.find(id)
- if acl == nil {
- return nil
- }
- e.acl = acl
- c.contents = c.contents.Put(e)
- return acl
-}
diff --git a/services/store/memstore/acl/checker.go b/services/store/memstore/acl/checker.go
deleted file mode 100644
index c3921e7..0000000
--- a/services/store/memstore/acl/checker.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package acl
-
-import (
- "bytes"
- "fmt"
-
- "veyron2/security"
- "veyron2/storage"
-)
-
-// Entry includes an ACL and flags to indicate whether the ACL should be inherited.
-type Entry struct {
- ACL *storage.ACL
- Inherited bool
-}
-
-// Set is a set of Entries, indexed by their IDs.
-type Set map[storage.ID]Entry
-
-// Checker is used to check if a principal matches ACLs extracted from the tags
-// applied to objects in a store.
-//
-// While walking through a path as part of resolving a cell, call Update for each
-// component of the path. Checker will then keep track of the inherited ACLs
-// for that path.
-type Checker struct {
- cache *Cache
- principal security.PublicID
- acls Set
-}
-
-// NewChecker constructs a new Checker and returns it.
-func NewChecker(cache *Cache, clientID security.PublicID, acls Set) *Checker {
- // Copy the Set.
- cp := make(Set)
- for id, acl := range acls {
- cp[id] = acl
- }
- return &Checker{cache: cache, principal: clientID, acls: cp}
-}
-
-// Copy, so that updates do not affect the original.
-func (c Checker) Copy() *Checker {
- acls := make(Set)
- for id, acl := range c.acls {
- acls[id] = acl
- }
- c.acls = acls
- return &c
-}
-
-// Update is called for each step in a path traversal to update the
-// Checker using the TagList associated with a value in the store.
-func (c *Checker) Update(tags storage.TagList) {
- // The caller has just made one step deeper into the path. The non-inherited
- // ACLs are no longer relevant, so prune them.
- for id, entry := range c.acls {
- if !entry.Inherited {
- delete(c.acls, id)
- }
- }
-
- // Add the new ACLc.
- for _, tag := range tags {
- switch tag.Op {
- case storage.RemoveACL:
- delete(c.acls, tag.ACL)
- case storage.AddACL:
- if acl := c.cache.get(tag.ACL); acl != nil {
- c.acls[tag.ACL] = Entry{ACL: acl}
- }
- case storage.AddInheritedACL:
- if acl := c.cache.get(tag.ACL); acl != nil {
- c.acls[tag.ACL] = Entry{ACL: acl, Inherited: true}
- }
- }
- }
-}
-
-// IsAllowed returns true iff the current acls allow the principal to use a
-// label.
-func (c *Checker) IsAllowed(label security.Label) bool {
- for _, entry := range c.acls {
- for key, labels := range entry.ACL.Contents {
- if labels.HasLabel(label) {
- if c.principal.Match(key) {
- return true
- }
- }
- }
- }
- return false
-}
-
-// IsEqual returns true iff the checkers are exactly equivalent, containing the same ACLs.
-func (c1 *Checker) IsEqual(c2 *Checker) bool {
- if c1.cache != c2.cache || c1.principal != c2.principal || len(c1.acls) != len(c2.acls) {
- return false
- }
-
- for id, _ := range c1.acls {
- if _, ok := c2.acls[id]; !ok {
- return false
- }
- }
- return true
-}
-
-func (c Checker) String() string {
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "Checker{principal:%q", c.principal)
- for p, l := range c.acls {
- fmt.Fprintf(&buf, ", %s:%s", p, l)
- }
- buf.WriteRune('}')
- return buf.String()
-}
-
-func (e Entry) String() string {
- if e.Inherited {
- return "[Inherited]" + e.ACL.String()
- }
- return e.ACL.String()
-}
diff --git a/services/store/memstore/refs/builder.go b/services/store/memstore/refs/builder.go
index 05f12a9..18582f2 100644
--- a/services/store/memstore/refs/builder.go
+++ b/services/store/memstore/refs/builder.go
@@ -6,7 +6,6 @@
"veyron/runtimes/google/lib/functional"
- "veyron2/security"
"veyron2/storage"
)
@@ -45,7 +44,7 @@
// AddDEntries adds the references contained in the DEntry list.
func (b *Builder) AddDEntries(d []*storage.DEntry) {
for _, de := range d {
- b.refs = b.refs.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name), Label: security.ReadLabel})
+ b.refs = b.refs.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name)})
}
}
@@ -58,20 +57,13 @@
b.addRefs(nil, reflect.ValueOf(v))
}
-// AddTags adds the references contained in the TagList.
-func (b *Builder) AddTags(v storage.TagList) {
- for i, tag := range v {
- b.refs.Put(&Ref{ID: tag.ACL, Path: tagsDir.Append(strconv.Itoa(i)), Label: security.AdminLabel})
- }
-}
-
func (b *Builder) addRefs(path *Path, v reflect.Value) {
if !v.IsValid() {
return
}
ty := v.Type()
if ty == tyID {
- b.refs = b.refs.Put(&Ref{ID: v.Interface().(storage.ID), Path: path, Label: security.ReadLabel})
+ b.refs = b.refs.Put(&Ref{ID: v.Interface().(storage.ID), Path: path})
return
}
switch ty.Kind() {
diff --git a/services/store/memstore/refs/path.go b/services/store/memstore/refs/path.go
index 5cb86db..4a151ee 100644
--- a/services/store/memstore/refs/path.go
+++ b/services/store/memstore/refs/path.go
@@ -21,15 +21,9 @@
table map[Path]*Path
}
-const (
- TagsDirName = ".tags"
-)
-
var (
pathTable = &pathHashConsTable{table: make(map[Path]*Path)}
nilPath *Path
-
- tagsDir = NewSingletonPath(TagsDirName)
)
// ComparePaths defines a total order over *Path values, based on pointer
diff --git a/services/store/memstore/refs/refs.go b/services/store/memstore/refs/refs.go
index 2576c73..c7f791b 100644
--- a/services/store/memstore/refs/refs.go
+++ b/services/store/memstore/refs/refs.go
@@ -5,7 +5,6 @@
"veyron/runtimes/google/lib/functional"
"veyron/runtimes/google/lib/functional/rb"
- "veyron2/security"
"veyron2/storage"
)
@@ -15,9 +14,8 @@
// Ref represents a single reference in a store value. It includes the
// storage.ID, and the path to the reference.
type Ref struct {
- ID storage.ID
- Path *Path
- Label security.Label
+ ID storage.ID
+ Path *Path
}
// Dir represents a directory, which is a set of *Ref, sorted by path.
@@ -58,7 +56,7 @@
func BuildDir(l []*storage.DEntry) Dir {
d := EmptyDir
for _, de := range l {
- d = d.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name), Label: security.ReadLabel})
+ d = d.Put(&Ref{ID: de.ID, Path: NewSingletonPath(de.Name)})
}
return d
}
diff --git a/services/store/memstore/state/cell.go b/services/store/memstore/state/cell.go
index 2913146..f0f9c32 100644
--- a/services/store/memstore/state/cell.go
+++ b/services/store/memstore/state/cell.go
@@ -23,10 +23,7 @@
// Implicit directory.
Dir refs.Set
- // tags are the access control tags.
- Tags storage.TagList
-
- // refs includes the references in the value, the dir, and the tags.
+ // refs includes the references in the value and the dir.
//
// TODO(jyh): This is simple, but it would be more space efficient to
// include only the refs in the value, or drop this field entirely.
@@ -69,7 +66,6 @@
r := refs.NewBuilder()
r.AddValue(c.Value)
r.AddDir(c.Dir)
- r.AddTags(c.Tags)
c.refs = r.Get()
}
diff --git a/services/store/memstore/state/iterator.go b/services/store/memstore/state/iterator.go
index 70eac5c..e7c5275 100644
--- a/services/store/memstore/state/iterator.go
+++ b/services/store/memstore/state/iterator.go
@@ -3,7 +3,6 @@
import (
"fmt"
- "veyron/services/store/memstore/acl"
"veyron/services/store/memstore/refs"
"veyron2/security"
@@ -59,12 +58,10 @@
}
type next struct {
- // checker is the acl.Checker for the object _containing_ the reference.
- checker *acl.Checker
- parent *refs.FullPath
- path *refs.Path
- id storage.ID
- action action
+ parent *refs.FullPath
+ path *refs.Path
+ id storage.ID
+ action action
}
type action int
@@ -116,8 +113,7 @@
func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName,
pathFilter PathFilter, filter IterFilter) Iterator {
- checker := sn.newPermChecker(pid)
- cell, suffix, v := sn.resolveCell(checker, path, nil)
+ cell, suffix, v := sn.resolveCell(path, nil)
if cell == nil {
return &errorIterator{snapshot: sn}
}
@@ -153,7 +149,7 @@
}
if expand {
- it.pushVisitAll(checker, it.path, set)
+ it.pushVisitAll(it.path, set)
}
if !ret {
it.Next()
@@ -165,7 +161,7 @@
func (it *iterator) pushUnvisit(path *refs.Path, id storage.ID) {
switch it.pathFilter {
case ListPaths:
- it.next = append(it.next, next{nil, nil, path, id, unvisit})
+ it.next = append(it.next, next{nil, path, id, unvisit})
case ListObjects:
// Do not unvisit the object, as it is on a path already seen by
// it.filter.
@@ -174,14 +170,11 @@
}
}
-func (it *iterator) pushVisitAll(checker *acl.Checker,
- parentPath *refs.FullPath, set refs.Set) {
+func (it *iterator) pushVisitAll(parentPath *refs.FullPath, set refs.Set) {
set.Iter(func(x interface{}) bool {
ref := x.(*refs.Ref)
- if checker.IsAllowed(ref.Label) {
- it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID, visit})
- }
+ it.next = append(it.next, next{parentPath, ref.Path, ref.ID, visit})
return true
})
}
@@ -205,7 +198,6 @@
func (it *iterator) Next() {
var n next
var fullPath *refs.FullPath
- var checker *acl.Checker
var c *Cell
for {
topIndex := len(it.next) - 1
@@ -235,18 +227,11 @@
panic(fmt.Sprintf("Dangling reference: %s", n.id))
}
- // Check permissions.
- checker = n.checker.Copy()
- checker.Update(c.Tags)
- if !checker.IsAllowed(security.ReadLabel) {
- continue
- }
-
// Check the filter
ret, expand := it.filter(n.parent, n.path)
fullPath = n.parent.AppendPath(n.path)
if expand {
- it.pushVisitAll(checker, fullPath, c.refs)
+ it.pushVisitAll(fullPath, c.refs)
}
if ret {
// Found a value.
diff --git a/services/store/memstore/state/iterator_test.go b/services/store/memstore/state/iterator_test.go
index a9f3b2b..ffc4bfc 100644
--- a/services/store/memstore/state/iterator_test.go
+++ b/services/store/memstore/state/iterator_test.go
@@ -163,107 +163,3 @@
{"teams/cardinals", "players/matt/team"},
})
}
-
-func TestIteratorSecurity(t *testing.T) {
- st := state.New(rootPublicID)
- sn := st.MutableSnapshot()
-
- // Create /Users/jane and give her RWA permissions.
- janeACLID := putPath(t, sn, rootPublicID, "/Users/jane/acls/janeRWA", &storage.ACL{
- Name: "Jane",
- Contents: security.ACL{
- janeUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
- },
- })
- janeTags := storage.TagList{
- storage.Tag{Op: storage.AddInheritedACL, ACL: janeACLID},
- storage.Tag{Op: storage.RemoveACL, ACL: state.EveryoneACLID},
- }
- put(t, sn, rootPublicID, "/Users/jane/.tags", janeTags)
- put(t, sn, rootPublicID, "/Users/jane/aaa", "stuff")
- sharedID := put(t, sn, rootPublicID, "/Users/jane/shared", "stuff")
-
- // Create /Users/john and give him RWA permissions.
- johnACLID := putPath(t, sn, rootPublicID, "/Users/john/acls/johnRWA", &storage.ACL{
- Name: "John",
- Contents: security.ACL{
- johnUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
- },
- })
- johnTags := storage.TagList{
- storage.Tag{Op: storage.AddInheritedACL, ACL: johnACLID},
- storage.Tag{Op: storage.RemoveACL, ACL: state.EveryoneACLID},
- }
- put(t, sn, rootPublicID, "/Users/john/.tags", johnTags)
- put(t, sn, rootPublicID, "/Users/john/aaa", "stuff")
- put(t, sn, rootPublicID, "/Users/john/shared", sharedID)
-
- // Root gets everything.
- checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
- "",
- "Users",
- "Users/jane",
- "Users/jane/acls",
- "Users/jane/acls/janeRWA",
- "Users/jane/aaa",
- "Users/john",
- "Users/john/acls",
- "Users/john/acls/johnRWA",
- "Users/john/aaa",
- "Users/jane/shared",
- "Users/john/shared",
- })
- checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
- {""},
- {"Users"},
- {"Users/jane"},
- {"Users/jane/acls"},
- {"Users/jane/acls/janeRWA"},
- {"Users/jane/aaa"},
- {"Users/john"},
- {"Users/john/acls"},
- {"Users/john/acls/johnRWA"},
- {"Users/john/aaa"},
- {"Users/jane/shared", "Users/john/shared"},
- })
-
- // Jane sees only her names.
- checkAcyclicIterator(t, sn, janePublicID, nil, []string{
- "",
- "Users",
- "Users/jane",
- "Users/jane/acls",
- "Users/jane/acls/janeRWA",
- "Users/jane/aaa",
- "Users/jane/shared",
- })
- checkUniqueObjectsIterator(t, sn, janePublicID, nil, [][]string{
- {""},
- {"Users"},
- {"Users/jane"},
- {"Users/jane/acls"},
- {"Users/jane/acls/janeRWA"},
- {"Users/jane/aaa"},
- {"Users/jane/shared"},
- })
-
- // John sees only his names.
- checkAcyclicIterator(t, sn, johnPublicID, nil, []string{
- "",
- "Users",
- "Users/john",
- "Users/john/acls",
- "Users/john/acls/johnRWA",
- "Users/john/aaa",
- "Users/john/shared",
- })
- checkUniqueObjectsIterator(t, sn, johnPublicID, nil, [][]string{
- {""},
- {"Users"},
- {"Users/john"},
- {"Users/john/acls"},
- {"Users/john/acls/johnRWA"},
- {"Users/john/aaa"},
- {"Users/john/shared"},
- })
-}
diff --git a/services/store/memstore/state/mutable_snapshot.go b/services/store/memstore/state/mutable_snapshot.go
index f640eb1..731d6ca 100644
--- a/services/store/memstore/state/mutable_snapshot.go
+++ b/services/store/memstore/state/mutable_snapshot.go
@@ -3,7 +3,6 @@
import (
"fmt"
- "veyron/services/store/memstore/acl"
"veyron/services/store/memstore/field"
"veyron/services/store/memstore/refs"
"veyron/services/store/raw"
@@ -88,11 +87,6 @@
// Value is the new value.
Value interface{}
- // Tags are the new tags.
- //
- // TODO(jyh): Replace with a delta encoding.
- Tags storage.TagList
-
// Dir is the set of new directory entries.
//
// TODO(jyh): Replace this with a delta, to support large directories.
@@ -108,8 +102,6 @@
errBadValue = verror.BadArgf("value has the wrong type")
errDuplicatePutMutation = verror.BadArgf("duplicate calls to PutMutation for the same ID")
errNotFound = verror.NotFoundf("not found")
- errNotTagList = verror.BadArgf("not a TagList")
- errPermissionDenied = verror.NotAuthorizedf("")
errPreconditionFailed = verror.Abortedf("precondition failed")
nullID storage.ID
@@ -168,7 +160,6 @@
// Perform a GC to clear out gcRoots.
sn.gc()
cp := sn.snapshot
- cp.resetACLCache()
return &cp
}
@@ -180,7 +171,6 @@
cp := *sn
cp.mutations = newMutations()
cp.gcRoots = make(map[storage.ID]struct{})
- cp.resetACLCache()
return &cp
}
@@ -206,7 +196,6 @@
func (sn *MutableSnapshot) delete(c *Cell) {
sn.idTable = sn.idTable.Remove(c)
sn.deletions[c.ID] = c.Version
- sn.aclCache.Invalidate(c.ID)
}
// put adds a cell to the state, also adding the new value to the Mutations set.
@@ -218,44 +207,32 @@
m.Value = c.Value
m.refs = c.refs
m.Dir = d
- m.Tags = c.Tags
} else {
mu.Preconditions[c.ID] = c.Version
m = &Mutation{
Postcondition: storage.NewVersion(),
Value: c.Value,
Dir: d,
- Tags: c.Tags,
refs: c.refs,
}
mu.Delta[c.ID] = m
}
c.Version = m.Postcondition
sn.idTable = sn.idTable.Put(c)
- sn.aclCache.Invalidate(c.ID)
}
// add adds a new Value to the state, updating reference counts. Fails if the
// new value contains dangling references.
-func (sn *MutableSnapshot) add(parentChecker *acl.Checker, id storage.ID, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) add(id storage.ID, v interface{}) (*Cell, error) {
c := sn.Find(id)
if c == nil {
// There is no current value, so create a new cell for the value and add
// it.
- //
- // There is no permissions check here because the caller is not modifying a preexisting value.
- //
- // TODO(jyh): However, the new value is created with default
- // permissions, which does not include the ability to set the tags on
- // the cell. So the caller can wind up in a odd situation where they
- // can create a value, but not be able to read it back, and no way to
- // fix it. Figure out whether this is a problem.
c = &Cell{
ID: id,
refcount: 0,
Value: v,
Dir: refs.EmptyDir,
- Tags: storage.TagList{},
inRefs: refs.Empty,
Version: storage.NoVersion,
}
@@ -269,16 +246,11 @@
}
// There is already a value in the state, so replace it with the new value.
- checker := parentChecker.Copy()
- checker.Update(c.Tags)
- return sn.replaceValue(checker, c, v)
+ return sn.replaceValue(c, v)
}
// replaceValue updates the cell.value.
-func (sn *MutableSnapshot) replaceValue(checker *acl.Checker, c *Cell, v interface{}) (*Cell, error) {
- if !checker.IsAllowed(security.WriteLabel) {
- return nil, errPermissionDenied
- }
+func (sn *MutableSnapshot) replaceValue(c *Cell, v interface{}) (*Cell, error) {
cp := *c
cp.Value = v
cp.setRefs()
@@ -291,10 +263,7 @@
}
// replaceDir updates the cell.dir.
-func (sn *MutableSnapshot) replaceDir(checker *acl.Checker, c *Cell, d functional.Set) (*Cell, error) {
- if !checker.IsAllowed(security.WriteLabel) {
- return nil, errPermissionDenied
- }
+func (sn *MutableSnapshot) replaceDir(c *Cell, d functional.Set) (*Cell, error) {
cp := *c
cp.Dir = d
cp.setRefs()
@@ -306,26 +275,9 @@
return &cp, nil
}
-// replaceTags replaces the cell.tags.
-func (sn *MutableSnapshot) replaceTags(checker *acl.Checker, c *Cell, tags storage.TagList) (*Cell, error) {
- if !checker.IsAllowed(security.AdminLabel) {
- return nil, errPermissionDenied
- }
- cp := *c
- cp.Tags = tags
- cp.setRefs()
- if !sn.refsExist(cp.refs) {
- return nil, errBadRef
- }
- sn.put(&cp)
- sn.updateRefs(c.ID, c.refs, cp.refs)
- return &cp, nil
-}
-
// Get returns the value for a path.
func (sn *MutableSnapshot) Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error) {
- checker := sn.newPermChecker(pid)
- cell, suffix, v := sn.resolveCell(checker, path, sn.mutations)
+ cell, suffix, v := sn.resolveCell(path, sn.mutations)
if cell == nil {
return nil, errNotFound
}
@@ -341,21 +293,20 @@
// Put adds a new value to the state or replaces an existing one. Returns
// the *Stat for the enclosing *cell.
func (sn *MutableSnapshot) Put(pid security.PublicID, path storage.PathName, v interface{}) (*storage.Stat, error) {
- checker := sn.newPermChecker(pid)
- c, err := sn.putValueByPath(checker, path, v)
+ c, err := sn.putValueByPath(path, v)
if err != nil {
return nil, err
}
return c.getStat(), nil
}
-func (sn *MutableSnapshot) putValueByPath(checker *acl.Checker, path storage.PathName, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) putValueByPath(path storage.PathName, v interface{}) (*Cell, error) {
v = deepcopy(v)
if path.IsRoot() {
- return sn.putRoot(checker, v)
+ return sn.putRoot(v)
}
- return sn.putValue(checker, path, v)
+ return sn.putValue(path, v)
}
// putValue is called for a normal Put() operation, where a new value is being
@@ -363,15 +314,12 @@
// There are two cases: 1) the value <v> is written directly into the parent, or
// 2) the field has type storage.ID. In the latter case, the <id> is assigned
// into the parent, and the value id->v is added to the idTable.
-func (sn *MutableSnapshot) putValue(checker *acl.Checker, path storage.PathName, v interface{}) (*Cell, error) {
+func (sn *MutableSnapshot) putValue(path storage.PathName, v interface{}) (*Cell, error) {
// Find the parent object.
- c, suffix, _ := sn.resolveCell(checker, path[:len(path)-1], sn.mutations)
+ c, suffix, _ := sn.resolveCell(path[:len(path)-1], sn.mutations)
if c == nil {
return nil, errNotFound
}
- if len(suffix) > 0 && suffix[0] == refs.TagsDirName {
- return sn.putTagsValue(checker, path, suffix[1:], c, v)
- }
value := deepcopy(c.Value)
p, s := field.Get(makeInnerReference(value), suffix)
if len(s) != 0 {
@@ -386,72 +334,28 @@
if len(suffix) != 0 {
return nil, errNotFound
}
- if name == refs.TagsDirName {
- return sn.putTags(checker, c, v)
- }
- return sn.putDirEntry(checker, c, name, v)
+ return sn.putDirEntry(c, name, v)
case field.SetFailedWrongType:
return nil, errBadValue
case field.SetAsID:
- nc, err := sn.add(checker, id, v)
+ nc, err := sn.add(id, v)
if err != nil {
return nil, err
}
// The sn.add may have modified the cell, so fetch it again.
- if _, err = sn.replaceValue(checker, sn.Find(c.ID), value); err != nil {
+ if _, err = sn.replaceValue(sn.Find(c.ID), value); err != nil {
return nil, err
}
return nc, nil
case field.SetAsValue:
- return sn.replaceValue(checker, c, value)
+ return sn.replaceValue(c, value)
}
panic("not reached")
}
-// putTagsValue modifies the cell.tags value.
-func (sn *MutableSnapshot) putTagsValue(checker *acl.Checker, path, suffix storage.PathName, c *Cell, v interface{}) (*Cell, error) {
- tags := deepcopy(c.Tags).(storage.TagList)
- p, s := field.Get(&tags, suffix)
- if len(s) != 0 {
- return nil, errNotFound
- }
-
- // Add value to the parent.
- name := path[len(path)-1]
- result, id := field.Set(p, name, v)
- switch result {
- case field.SetFailedNotFound:
- return nil, errNotFound
- case field.SetFailedWrongType:
- return nil, errBadValue
- case field.SetAsID:
- nc, err := sn.add(checker, id, v)
- if err != nil {
- return nil, err
- }
- // The sn.add may have modified the cell, so fetch it again.
- if _, err = sn.replaceTags(checker, sn.Find(c.ID), tags); err != nil {
- return nil, err
- }
- return nc, nil
- case field.SetAsValue:
- return sn.replaceTags(checker, c, tags)
- }
- panic("not reached")
-}
-
-// putTags updates the tags.
-func (sn *MutableSnapshot) putTags(checker *acl.Checker, c *Cell, v interface{}) (*Cell, error) {
- tags, ok := v.(storage.TagList)
- if !ok {
- return nil, errNotTagList
- }
- return sn.replaceTags(checker, c, tags)
-}
-
// putDirEntry replaces or adds a directory entry.
-func (sn *MutableSnapshot) putDirEntry(checker *acl.Checker, c *Cell, name string, v interface{}) (*Cell, error) {
- r := &refs.Ref{Path: refs.NewSingletonPath(name), Label: security.ReadLabel}
+func (sn *MutableSnapshot) putDirEntry(c *Cell, name string, v interface{}) (*Cell, error) {
+ r := &refs.Ref{Path: refs.NewSingletonPath(name)}
if id, ok := v.(storage.ID); ok {
ncell := sn.Find(id)
if ncell == nil {
@@ -459,7 +363,7 @@
}
r.ID = id
dir := c.Dir.Put(r)
- if _, err := sn.replaceDir(checker, c, dir); err != nil {
+ if _, err := sn.replaceDir(c, dir); err != nil {
return nil, err
}
return ncell, nil
@@ -469,7 +373,7 @@
if !ok {
// The entry does not exist yet; create it.
id := storage.NewID()
- ncell, err := sn.add(checker, id, v)
+ ncell, err := sn.add(id, v)
if err != nil {
return nil, err
}
@@ -477,22 +381,18 @@
// The sn.add may have modified the cell, so fetch it again.
c = sn.Find(c.ID)
dir := c.Dir.Put(r)
- if _, err := sn.replaceDir(checker, c, dir); err != nil {
+ if _, err := sn.replaceDir(c, dir); err != nil {
return nil, err
}
return ncell, nil
}
// Replace the existing value.
- return sn.add(checker, x.(*refs.Ref).ID, v)
+ return sn.add(x.(*refs.Ref).ID, v)
}
// putRoot replaces the root.
-func (sn *MutableSnapshot) putRoot(checker *acl.Checker, v interface{}) (*Cell, error) {
- if !checker.IsAllowed(security.WriteLabel) {
- return nil, errPermissionDenied
- }
-
+func (sn *MutableSnapshot) putRoot(v interface{}) (*Cell, error) {
id := sn.rootID
c := sn.Find(id)
if c == nil {
@@ -500,7 +400,7 @@
}
// Add the new element.
- ncell, err := sn.add(checker, id, v)
+ ncell, err := sn.add(id, v)
if err != nil {
return nil, err
}
@@ -517,11 +417,7 @@
// Remove removes a value.
func (sn *MutableSnapshot) Remove(pid security.PublicID, path storage.PathName) error {
- checker := sn.newPermChecker(pid)
if path.IsRoot() {
- if !checker.IsAllowed(security.WriteLabel) {
- return errPermissionDenied
- }
sn.unref(sn.rootID)
sn.rootID = nullID
sn.mutations.RootID = nullID
@@ -530,20 +426,16 @@
}
// Split the names into directory and field parts.
- cell, suffix, _ := sn.resolveCell(checker, path[:len(path)-1], sn.mutations)
+ cell, suffix, _ := sn.resolveCell(path[:len(path)-1], sn.mutations)
if cell == nil {
return errNotFound
}
// Remove the field.
name := path[len(path)-1]
- if name == refs.TagsDirName {
- _, err := sn.replaceTags(checker, cell, storage.TagList{})
- return err
- }
- r := &refs.Ref{Path: refs.NewSingletonPath(name), Label: security.ReadLabel}
+ r := &refs.Ref{Path: refs.NewSingletonPath(name)}
if cell.Dir.Contains(r) {
- _, err := sn.replaceDir(checker, cell, cell.Dir.Remove(r))
+ _, err := sn.replaceDir(cell, cell.Dir.Remove(r))
return err
}
value := deepcopy(cell.Value)
@@ -552,7 +444,7 @@
return errNotFound
}
- _, err := sn.replaceValue(checker, cell, value)
+ _, err := sn.replaceValue(cell, value)
return err
}
diff --git a/services/store/memstore/state/mutable_snapshot_test.go b/services/store/memstore/state/mutable_snapshot_test.go
index 34366c6..df567ba 100644
--- a/services/store/memstore/state/mutable_snapshot_test.go
+++ b/services/store/memstore/state/mutable_snapshot_test.go
@@ -75,7 +75,7 @@
func expectValue(t *testing.T, sn *MutableSnapshot, path string, v interface{}) {
_, file, line, _ := runtime.Caller(1)
- cell, _, _ := sn.resolveCell(sn.newPermChecker(rootPublicID), storage.ParsePath(path), nil)
+ cell, _, _ := sn.resolveCell(storage.ParsePath(path), nil)
if cell == nil {
t.Errorf("%s(%d): path does not exist: %s", file, line, path)
}
diff --git a/services/store/memstore/state/perm.go b/services/store/memstore/state/perm.go
index 712be36..7bf2df5 100644
--- a/services/store/memstore/state/perm.go
+++ b/services/store/memstore/state/perm.go
@@ -1,67 +1 @@
package state
-
-import (
- "veyron/services/store/memstore/acl"
-
- "veyron2/security"
- "veyron2/storage"
-)
-
-var (
- // adminACLID is the storage.ID used for the administrator's default ACL.
- AdminACLID = storage.ID{0}
-
- // everyoneACLID is the storage.ID used for the default ACL for non-administrators.
- EveryoneACLID = storage.ID{1}
-
- // uidTagList is the storage.TagList for the /uid directory. It ensures that
- // /uid/* is accessible only to the administrators of the storage.
- //
- // TODO(jyh): Consider having an actual /uid object, so that the
- // administrator could configure permissions on it.
- uidTagList = storage.TagList{storage.Tag{Op: storage.RemoveACL, ACL: EveryoneACLID}}
-)
-
-// makeDefaultACLSet returns the default ACL for the store, allowing admin
-// universal access, and everyone else gets readonly access.
-func makeDefaultACLSet(admin security.PublicID) acl.Set {
- adminContents := security.ACL{}
- for _, name := range admin.Names() {
- adminContents[security.PrincipalPattern(name)] = security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel)
- }
- adminACL := &storage.ACL{
- Name: "admin",
- Contents: adminContents,
- }
- everyoneACL := &storage.ACL{
- Name: "everyone",
- Contents: security.ACL{security.AllPrincipals: security.LabelSet(security.ReadLabel)},
- }
- return acl.Set{
- AdminACLID: acl.Entry{ACL: adminACL, Inherited: true},
- EveryoneACLID: acl.Entry{ACL: everyoneACL, Inherited: true},
- }
-}
-
-// newPermChecker returns a new acl.Checker in the current state.
-func (sn *snapshot) newPermChecker(pid security.PublicID) *acl.Checker {
- return acl.NewChecker(&sn.aclCache, pid, sn.defaultACLSet)
-}
-
-// makeFindACLFunc returns a function to fetch ACL values from the storage.
-func (sn *snapshot) makeFindACLFunc() acl.FindFunc {
- return func(id storage.ID) *storage.ACL {
- v, ok := sn.idTable.Get(&Cell{ID: id})
- if !ok {
- return nil
- }
- x := v.(*Cell).Value
- if acl, ok := x.(*storage.ACL); ok {
- return acl
- }
- if acl, ok := x.(storage.ACL); ok {
- return &acl
- }
- return nil
- }
-}
diff --git a/services/store/memstore/state/refs.go b/services/store/memstore/state/refs.go
index a131339..77c8560 100644
--- a/services/store/memstore/state/refs.go
+++ b/services/store/memstore/state/refs.go
@@ -67,13 +67,13 @@
// Add the inverse link.
c := sn.Find(r.ID)
- c.inRefs = c.inRefs.Put(&refs.Ref{ID: id, Path: r.Path, Label: r.Label})
+ c.inRefs = c.inRefs.Put(&refs.Ref{ID: id, Path: r.Path})
}
func (sn *MutableSnapshot) removeRef(id storage.ID, r *refs.Ref) {
// Remove the inverse link.
c := sn.deref(r.ID)
- c.inRefs = c.inRefs.Remove(&refs.Ref{ID: id, Path: r.Path, Label: r.Label})
+ c.inRefs = c.inRefs.Remove(&refs.Ref{ID: id, Path: r.Path})
// Update refcount.
sn.unref(r.ID)
diff --git a/services/store/memstore/state/security_test.go b/services/store/memstore/state/security_test.go
deleted file mode 100644
index 1c0ddfb..0000000
--- a/services/store/memstore/state/security_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package state_test
-
-import (
- "reflect"
- "testing"
-
- "veyron/services/store/memstore/state"
-
- "veyron2/security"
- "veyron2/storage"
-)
-
-func TestSecurity(t *testing.T) {
- st := state.New(rootPublicID)
- sn := st.MutableSnapshot()
-
- // Create /Users/jane and give her RWA permissions.
- aclID := putPath(t, sn, rootPublicID, "/Users/jane/acls/janeRWA", &storage.ACL{
- Name: "Jane",
- Contents: security.ACL{
- janeUser: security.LabelSet(security.ReadLabel | security.WriteLabel | security.AdminLabel),
- },
- })
- janeTags := storage.TagList{storage.Tag{Op: storage.AddInheritedACL, ACL: aclID}}
- put(t, sn, rootPublicID, "/Users/jane/.tags", janeTags)
-
- // John should not be able to do anything.
- {
- if _, err := maybePut(sn, johnPublicID, "/", Node{}); err == nil {
- t.Errorf("Security violation")
- }
- if _, err := maybePut(sn, johnPublicID, "/Users/jane", Node{}); err == nil {
- t.Errorf("Security violation")
- }
- if _, err := maybeGet(sn, johnPublicID, "/Users/jane/.tags"); err == nil {
- t.Errorf("Security violation")
- }
- }
-
- // Jane can access her own directory.
- {
- if _, err := maybePut(sn, janePublicID, "/", Node{}); err == nil {
- t.Errorf("Security violation")
- }
- if _, err := maybeGet(sn, janePublicID, "/Users/jane"); err != nil {
- t.Errorf("Expected /Users/jane to exist: %s", err)
- }
- if _, err := maybePut(sn, janePublicID, "/Users/jane", Node{}); err != nil {
- t.Errorf("Unexpected security error: %s %s", err, janePublicID)
- }
- if tags, err := maybeGet(sn, janePublicID, "/Users/jane/.tags"); err == nil {
- if !reflect.DeepEqual(janeTags, tags) {
- t.Errorf("Expected %+v, got %+v", janeTags, tags)
- }
- } else {
- t.Errorf("Unexpected security error: %s", err)
- }
- }
-
- // Jane gives John read/write permission.
- var johnTag storage.Tag
- {
- aclID := putPath(t, sn, janePublicID, "/Users/jane/acls/johnRW", storage.ACL{
- Name: "John",
- Contents: security.ACL{
- johnUser: security.LabelSet(security.ReadLabel | security.WriteLabel),
- },
- })
- johnTag = storage.Tag{Op: storage.AddInheritedACL, ACL: aclID}
- // The @ is a pseudo-index, meaning append the tag to the list of tags.
- put(t, sn, janePublicID, "/Users/jane/.tags/@", johnTag)
- }
-
- // Jane can still access.
- janeTags = append(janeTags, johnTag)
- {
- if _, err := maybeGet(sn, janePublicID, "/Users/jane"); err != nil {
- t.Errorf("Expected /Users/jane to exist: %s", err)
- }
- if _, err := maybePut(sn, janePublicID, "/Users/jane", Node{}); err != nil {
- t.Errorf("Unexpected security error: %s", err)
- }
- if tags, err := maybeGet(sn, janePublicID, "/Users/jane/.tags"); err == nil {
- if !reflect.DeepEqual(janeTags, tags) {
- t.Errorf("Expected %+v, got %+v", janeTags, tags)
- }
- } else {
- t.Errorf("Unexpected security error: %s", err)
- }
- }
-
- // John also has access.
- {
- if _, err := maybePut(sn, johnPublicID, "/Users/jane", Node{}); err != nil {
- t.Errorf("Unexpected error: %s", err)
- }
- mkdir(t, sn, johnPublicID, "/Users/jane/john")
-
- // John is still not allowed to access the tags.
- if _, err := maybeGet(sn, johnPublicID, "/Users/jane/.tags"); err == nil {
- t.Errorf("Security violation")
- }
- }
-}
diff --git a/services/store/memstore/state/snapshot.go b/services/store/memstore/state/snapshot.go
index df7f209..80f6372 100644
--- a/services/store/memstore/state/snapshot.go
+++ b/services/store/memstore/state/snapshot.go
@@ -3,7 +3,6 @@
import (
"reflect"
- "veyron/services/store/memstore/acl"
"veyron/services/store/memstore/field"
"veyron/services/store/memstore/refs"
@@ -53,29 +52,16 @@
// rootID is the identifier of the root object.
rootID storage.ID
-
- // aclCache caches a set of ACLs.
- aclCache acl.Cache
-
- // defaultACLSet is the ACLSet used to access the root directory.
- defaultACLSet acl.Set
}
// newSnapshot returns an empty snapshot.
func newSnapshot(admin security.PublicID) snapshot {
sn := snapshot{
- idTable: emptyIDTable,
- defaultACLSet: makeDefaultACLSet(admin),
+ idTable: emptyIDTable,
}
- sn.aclCache = acl.NewCache(sn.makeFindACLFunc())
return sn
}
-// resetACLCache resets the aclCache.
-func (sn *snapshot) resetACLCache() {
- sn.aclCache.UpdateFinder(sn.makeFindACLFunc())
-}
-
// Find performs a lookup based on storage.ID, returning nil if the cell is not found.
func (sn *snapshot) Find(id storage.ID) *Cell {
v, ok := sn.idTable.Get(&Cell{ID: id})
@@ -87,9 +73,8 @@
// Get implements the Snapshot method.
func (sn *snapshot) Get(pid security.PublicID, path storage.PathName) (*storage.Entry, error) {
- checker := sn.newPermChecker(pid)
// Pass nil for 'mutations' since the snapshot is immutable.
- cell, suffix, v := sn.resolveCell(checker, path, nil)
+ cell, suffix, v := sn.resolveCell(path, nil)
if cell == nil {
return nil, errNotFound
}
@@ -107,7 +92,7 @@
// Returns (cell, suffix, v), where cell contains the value, suffix is the path
// to the value, v is the value itself. If the operation failed, the returned
// cell is nil.
-func (sn *snapshot) resolveCell(checker *acl.Checker, path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
+func (sn *snapshot) resolveCell(path storage.PathName, mu *Mutations) (*Cell, storage.PathName, interface{}) {
cell := sn.Find(sn.rootID)
if cell == nil {
return nil, nil, nil
@@ -116,23 +101,9 @@
if mu != nil {
mu.addPrecondition(cell)
}
- checker.Update(cell.Tags)
var v reflect.Value
var suffix storage.PathName
- if len(path) > 0 && path[0] == refs.TagsDirName {
- if !checker.IsAllowed(security.AdminLabel) {
- // Access to .tags requires admin priviledges.
- return nil, nil, errPermissionDenied
- }
- v, suffix = field.Get(cell.Tags, path[1:])
- } else {
- if !checker.IsAllowed(security.ReadLabel) {
- // Do not return errPermissionDenied because that would leak the
- // existence of the inaccessible value.
- return nil, nil, nil
- }
- v, suffix = field.Get(cell.Value, path)
- }
+ v, suffix = field.Get(cell.Value, path)
x := v.Interface()
if id, ok := x.(storage.ID); ok {
// Always dereference IDs.
diff --git a/services/store/memstore/state/state.go b/services/store/memstore/state/state.go
index 04f914c..c398b86 100644
--- a/services/store/memstore/state/state.go
+++ b/services/store/memstore/state/state.go
@@ -75,11 +75,6 @@
}
// ApplyMutations applies a set of mutations atomically.
-//
-// We don't need to check permissions because:
-// 1. Permissions were checked as the mutations were created.
-// 2. Preconditions ensure that all paths to modified values haven't changed.
-// 3. The client cannot fabricate a mutations value.
func (st *State) ApplyMutations(mu *Mutations) error {
// Assign a timestamp.
ts := uint64(time.Now().UnixNano())
@@ -125,14 +120,12 @@
for id, m := range mu.Delta {
d := refs.BuildDir(m.Dir)
cl, ok := table.Get(&Cell{ID: id})
- sn.aclCache.Invalidate(id)
if !ok {
c := &Cell{
ID: id,
Version: m.Postcondition,
Value: m.Value,
Dir: d,
- Tags: m.Tags,
refs: m.refs,
inRefs: refs.Empty,
}
@@ -144,7 +137,6 @@
cp.Version = m.Postcondition
cp.Value = m.Value
cp.Dir = d
- cp.Tags = m.Tags
cp.refs = m.refs
table = table.Put(&cp)
updates = append(updates, &refUpdate{id: c.ID, before: c.refs, after: cp.refs})
diff --git a/services/store/memstore/state/state_test.go b/services/store/memstore/state/state_test.go
index 1933631..7a8045f 100644
--- a/services/store/memstore/state/state_test.go
+++ b/services/store/memstore/state/state_test.go
@@ -16,14 +16,6 @@
var (
rootPublicID security.PublicID = security.FakePublicID("root")
- johnPublicID security.PublicID = security.FakePublicID("john")
- janePublicID security.PublicID = security.FakePublicID("jane")
- joanPublicID security.PublicID = security.FakePublicID("joan")
-
- rootUser = security.PrincipalPattern("fake/root")
- johnUser = security.PrincipalPattern("fake/john")
- janeUser = security.PrincipalPattern("fake/jane")
- joanUser = security.PrincipalPattern("fake/joan")
)
// makeParentNodes creates the parent nodes if they do not already exist.
diff --git a/services/store/memstore/watch/raw_processor.go b/services/store/memstore/watch/raw_processor.go
index 68dc10a..962e0bd 100644
--- a/services/store/memstore/watch/raw_processor.go
+++ b/services/store/memstore/watch/raw_processor.go
@@ -83,7 +83,6 @@
Version: cell.Version,
IsRoot: isRoot,
Value: cell.Value,
- Tags: cell.Tags,
Dir: flattenDir(refs.FlattenDir(cell.Dir)),
}
change := watch.Change{
@@ -148,7 +147,6 @@
Version: mu.Postcondition,
IsRoot: isRoot,
Value: mu.Value,
- Tags: mu.Tags,
Dir: flattenDir(mu.Dir),
}
// TODO(tilaks): don't clone value.
diff --git a/services/store/raw/service.vdl b/services/store/raw/service.vdl
index 3384837..4ec7e2c 100644
--- a/services/store/raw/service.vdl
+++ b/services/store/raw/service.vdl
@@ -38,9 +38,6 @@
// Value is value stored at this entry.
Value any
- // Tags specify permissions on this entry.
- Tags storage.TagList
-
// Dir is the implicit directory of this entry, and may contain references
// to other entries in the store.
Dir []storage.DEntry
diff --git a/services/store/raw/service.vdl.go b/services/store/raw/service.vdl.go
index 0cbbb42..1eb0cfd 100644
--- a/services/store/raw/service.vdl.go
+++ b/services/store/raw/service.vdl.go
@@ -35,8 +35,6 @@
IsRoot bool
// Value is value stored at this entry.
Value _gen_vdlutil.Any
- // Tags specify permissions on this entry.
- Tags storage.TagList
// Dir is the implicit directory of this entry, and may contain references
// to other entries in the store.
Dir []storage.DEntry
@@ -479,7 +477,7 @@
OutArgs: []_gen_ipc.MethodArgument{
{Name: "", Type: 68},
},
- InStream: 80,
+ InStream: 77,
}
result.Methods["Watch"] = _gen_ipc.MethodSignature{
InArgs: []_gen_ipc.MethodArgument{
@@ -512,27 +510,20 @@
_gen_wiretype.FieldType{Type: 0x47, Name: "Changes"},
},
"veyron2/services/watch.ChangeBatch", []string(nil)},
- _gen_wiretype.ArrayType{Elem: 0x41, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/storage.TagOp", Tags: []string(nil)}, _gen_wiretype.StructType{
- []_gen_wiretype.FieldType{
- _gen_wiretype.FieldType{Type: 0x4b, Name: "Op"},
- _gen_wiretype.FieldType{Type: 0x49, Name: "ACL"},
- },
- "veyron2/storage.Tag", []string(nil)},
- _gen_wiretype.SliceType{Elem: 0x4c, Name: "veyron2/storage.TagList", Tags: []string(nil)}, _gen_wiretype.StructType{
+ _gen_wiretype.ArrayType{Elem: 0x41, Len: 0x10, Name: "veyron2/storage.ID", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x35, Name: "veyron2/storage.Version", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
_gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
_gen_wiretype.FieldType{Type: 0x49, Name: "ID"},
},
"veyron2/storage.DEntry", []string(nil)},
- _gen_wiretype.SliceType{Elem: 0x4e, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+ _gen_wiretype.SliceType{Elem: 0x4b, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
_gen_wiretype.FieldType{Type: 0x49, Name: "ID"},
_gen_wiretype.FieldType{Type: 0x4a, Name: "PriorVersion"},
_gen_wiretype.FieldType{Type: 0x4a, Name: "Version"},
_gen_wiretype.FieldType{Type: 0x2, Name: "IsRoot"},
_gen_wiretype.FieldType{Type: 0x45, Name: "Value"},
- _gen_wiretype.FieldType{Type: 0x4d, Name: "Tags"},
- _gen_wiretype.FieldType{Type: 0x4f, Name: "Dir"},
+ _gen_wiretype.FieldType{Type: 0x4c, Name: "Dir"},
},
"veyron/services/store/raw.Mutation", []string(nil)},
}