Merge "veyron2/vom2: Support typeobject decoding."
diff --git a/runtimes/GO.PACKAGE b/runtimes/GO.PACKAGE
index b80bb38..729132d 100644
--- a/runtimes/GO.PACKAGE
+++ b/runtimes/GO.PACKAGE
@@ -2,9 +2,10 @@
"dependencies": {
"incoming": [
{"allow": "veyron.io/veyron/veyron/runtimes/..."},
- {"allow": "veyron.io/veyron/veyron/services/...", "comment": "temporarily allowing dependency from services"},
{"allow": "veyron.io/veyron/veyron2/rt/..."},
{"allow": "veyron.io/veyron/veyron/lib/...", "comment":"temporarily allowing dependency from lib"},
+ {"allow": "veyron.io/veyron/veyron/profiles/...", "comment":"temporarily allowing dependency from profiles"},
+ {"allow": "veyron.io/veyron/veyron/services/...", "comment": "temporarily allowing dependency from services"},
{"deny": "..."}
]
}
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 8451c3e..c4903d2 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -590,13 +590,15 @@
// validation for a server's blessings are eanbled.
// Returning zero values affects more than third-party caveats, so yeah, have
// to remove them soon!
-func (c serverAuthContext) Timestamp() time.Time { return c.timestamp }
-func (serverAuthContext) Method() string { return "" }
-func (serverAuthContext) MethodTags() []interface{} { return nil }
-func (serverAuthContext) Name() string { return "" }
-func (serverAuthContext) Suffix() string { return "" }
-func (serverAuthContext) Label() (l security.Label) { return l }
-func (serverAuthContext) Discharges() map[string]security.Discharge { return nil }
+func (c serverAuthContext) Timestamp() time.Time { return c.timestamp }
+func (serverAuthContext) Method() string { return "" }
+func (serverAuthContext) MethodTags() []interface{} { return nil }
+func (serverAuthContext) Name() string { return "" }
+func (serverAuthContext) Suffix() string { return "" }
+func (serverAuthContext) Label() (l security.Label) { return l }
+
+// TODO(ataly): Remove this once the method is added to the flow type?
+func (serverAuthContext) RemoteDischarges() map[string]security.Discharge { return nil }
func splitObjectName(name string) (mtPattern, serverPattern security.BlessingPattern, objectName string) {
objectName = name
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 522ead9..649e8b4 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -22,6 +22,8 @@
"veyron.io/veyron/veyron2/vtrace"
"veyron.io/veyron/veyron/lib/netstate"
+ "veyron.io/veyron/veyron/lib/stats"
+ "veyron.io/veyron/veyron/runtimes/google/ipc/stream/vc"
"veyron.io/veyron/veyron/runtimes/google/lib/publisher"
inaming "veyron.io/veyron/veyron/runtimes/google/naming"
ivtrace "veyron.io/veyron/veyron/runtimes/google/vtrace"
@@ -68,6 +70,7 @@
func InternalNewServer(ctx context.T, streamMgr stream.Manager, ns naming.Namespace, store *ivtrace.Store, opts ...ipc.ServerOpt) (ipc.Server, error) {
ctx, _ = ivtrace.WithNewSpan(ctx, "NewServer")
+ statsPrefix := naming.Join("ipc", "server", "routing-id", streamMgr.RoutingID().String())
s := &server{
ctx: ctx,
streamMgr: streamMgr,
@@ -75,20 +78,40 @@
listeners: make(map[stream.Listener]*dhcpListener),
stoppedChan: make(chan struct{}),
ns: ns,
- stats: newIPCStats(naming.Join("ipc", "server", streamMgr.RoutingID().String())),
+ stats: newIPCStats(statsPrefix),
traceStore: store,
}
+ var (
+ principal security.Principal
+ blessings security.Blessings
+ )
for _, opt := range opts {
switch opt := opt.(type) {
case stream.ListenerOpt:
// Collect all ServerOpts that are also ListenerOpts.
s.listenerOpts = append(s.listenerOpts, opt)
+ switch opt := opt.(type) {
+ case vc.LocalPrincipal:
+ principal = opt.Principal
+ case options.ServerBlessings:
+ blessings = opt.Blessings
+ }
case options.ServesMountTable:
s.servesMountTable = bool(opt)
case options.ReservedNameDispatcher:
s.reservedOpt = opt
}
}
+ blessingsStatsName := naming.Join(statsPrefix, "security", "blessings")
+ if blessings != nil {
+ // TODO(caprita): revist printing the blessings with %s, and
+ // instead expose them as a list.
+ stats.NewString(blessingsStatsName).Set(fmt.Sprintf("%s", blessings))
+ } else if principal != nil { // principal should have been passed in, but just in case.
+ stats.NewStringFunc(blessingsStatsName, func() string {
+ return fmt.Sprintf("%s (default)", principal.BlessingStore().Default())
+ })
+ }
return s, nil
}
@@ -472,11 +495,14 @@
s.Lock()
defer s.Unlock()
ivtrace.FromContext(s.ctx).Annotate("Serving under name: " + name)
+ if len(name) == 0 {
+ return fmt.Errorf("empty name")
+ }
if s.stopped {
return errServerStopped
}
- if len(name) == 0 {
- return fmt.Errorf("empty name")
+ if s.disp == nil {
+ return fmt.Errorf("Adding name before calling Serve or ServeDispatcher is not allowed")
}
s.publisher.AddName(name)
// TODO(cnicolaou): remove this map when the publisher's RemoveName
@@ -492,6 +518,9 @@
if s.stopped {
return errServerStopped
}
+ if s.disp == nil {
+ return fmt.Errorf("Removing name before calling Serve or ServeDispatcher is not allowed")
+ }
if _, present := s.names[name]; !present {
return fmt.Errorf("%q has not been previously used for this server", name)
}
@@ -898,7 +927,7 @@
// Implementations of ipc.ServerContext methods.
-func (fs *flowServer) Discharges() map[string]security.Discharge {
+func (fs *flowServer) RemoteDischarges() map[string]security.Discharge {
//nologcall
return fs.discharges
}
diff --git a/runtimes/google/ipc/stats.go b/runtimes/google/ipc/stats.go
index 5f6f2ed..334ce23 100644
--- a/runtimes/google/ipc/stats.go
+++ b/runtimes/google/ipc/stats.go
@@ -48,7 +48,7 @@
defer s.mu.Unlock()
m, ok := s.methods[method]
if !ok {
- name := naming.Join(s.prefix, method, "latency-ms")
+ name := naming.Join(s.prefix, "methods", method, "latency-ms")
s.methods[method] = &perMethodStats{
latency: stats.NewHistogram(name, histogram.Options{
NumBuckets: 25,
diff --git a/runtimes/google/ipc/stream/vc/auth.go b/runtimes/google/ipc/stream/vc/auth.go
index 439ead8..5fe37cf 100644
--- a/runtimes/google/ipc/stream/vc/auth.go
+++ b/runtimes/google/ipc/stream/vc/auth.go
@@ -5,13 +5,11 @@
"errors"
"fmt"
"io"
- "time"
"veyron.io/veyron/veyron/runtimes/google/ipc/stream/crypto"
"veyron.io/veyron/veyron/runtimes/google/lib/iobuf"
"veyron.io/veyron/veyron2/ipc/version"
- "veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/vom"
)
@@ -58,10 +56,9 @@
if server, err = readBlessings(conn, authServerContextTag, crypter, v); err != nil {
return nil, nil, err
}
- serverB := server.ForContext(&serverAuthContext{
- self: principal,
- remote: server,
- timestamp: time.Now(),
+ serverB := server.ForContext(security.NewContext(&security.ContextParams{
+ LocalPrincipal: principal,
+ RemoteBlessings: server,
// TODO(ashankar): Get the local and remote endpoint here?
// There is also a bootstrapping problem here. For example, let's say
// (1) server has the blessing "provider/server" with a PeerIdentity caveat of "provider/client"
@@ -69,7 +66,7 @@
// How do we get that working?
// One option is to have a UnionOfBlessings of all blessings of the client in the BlessingStore
// made available to serverAuthContext.LocalBlessings for this call.
- })
+ }))
client = principal.BlessingStore().ForPeer(serverB...)
if client == nil {
return nil, nil, fmt.Errorf("no blessing tagged for peer %v in the BlessingStore", serverB)
@@ -132,24 +129,3 @@
}
return b, nil
}
-
-// security.Context implementation used when extracting blessings from what the
-// server presents during authentication.
-type serverAuthContext struct {
- self security.Principal
- remote security.Blessings
- timestamp time.Time
-}
-
-func (c *serverAuthContext) Timestamp() time.Time { return c.timestamp }
-func (*serverAuthContext) Method() string { return "" }
-func (*serverAuthContext) MethodTags() []interface{} { return nil }
-func (*serverAuthContext) Name() string { return "" }
-func (*serverAuthContext) Suffix() string { return "" }
-func (*serverAuthContext) Label() (l security.Label) { return l }
-func (c *serverAuthContext) Discharges() map[string]security.Discharge { return nil }
-func (c *serverAuthContext) LocalPrincipal() security.Principal { return c.self }
-func (c *serverAuthContext) LocalBlessings() security.Blessings { return nil }
-func (c *serverAuthContext) RemoteBlessings() security.Blessings { return c.remote }
-func (c *serverAuthContext) LocalEndpoint() naming.Endpoint { return nil }
-func (c *serverAuthContext) RemoteEndpoint() naming.Endpoint { return nil }
diff --git a/runtimes/google/lib/follow/bsd_darwin_linux_windows_config.go b/runtimes/google/lib/follow/bsd_darwin_linux_windows_config.go
deleted file mode 100644
index 09a9cf9..0000000
--- a/runtimes/google/lib/follow/bsd_darwin_linux_windows_config.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build darwin freebsd linux netbsd openbsd windows
-
-package follow
-
-// newFSWatcher starts and returns a new fsnotify-based fsWatcher.
-// filename specifies the file to watch.
-func newFSWatcher(filename string) (fsWatcher, error) {
- return newFSNotifyWatcher(filename)
-}
diff --git a/runtimes/google/lib/follow/notify_reader_test.go b/runtimes/google/lib/follow/notify_reader_test.go
deleted file mode 100644
index 380f6ce..0000000
--- a/runtimes/google/lib/follow/notify_reader_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// +build darwin freebsd linux netbsd openbsd windows
-
-package follow
-
-import (
- "io/ioutil"
- "os"
- "testing"
- "time"
-)
-
-// TestNotifyReadPartial tests partial reads with the fsnotify-based fsReader
-func TestNotifyReadPartial(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.notify.partial")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the fsnotify-based fsWatcher.
- watcher, err := newFSNotifyWatcher(testFileName)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testReadPartial(testFileName, watcher, timeout); err != nil {
- t.Fatal("testReadPartial() failed: %v", err)
- }
-}
-
-// TestNotifyReadFull tests full reads with the fsnotify-based fsReader
-func TestNotifyReadFull(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.notify.full")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the fsnotify-based fsWatcher.
- watcher, err := newFSNotifyWatcher(testFileName)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testReadFull(testFileName, watcher, timeout); err != nil {
- t.Fatal("testReadFull() failed: %v", err)
- }
-}
-
-// TestNotifyClose tests close with the fsnotify-based fsReader
-func TestNotifyClose(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.notify.close")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the fsnotify-based fsWatcher.
- watcher, err := newFSNotifyWatcher(testFileName)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testClose(testFileName, watcher, timeout); err != nil {
- t.Fatal("testClose() failed: %v", err)
- }
-}
diff --git a/runtimes/google/lib/follow/notify_watcher.go b/runtimes/google/lib/follow/notify_watcher.go
deleted file mode 100644
index ed79e9f..0000000
--- a/runtimes/google/lib/follow/notify_watcher.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// +build darwin freebsd linux netbsd openbsd windows
-
-package follow
-
-import (
- "fmt"
- "github.com/howeyc/fsnotify"
- "io"
- "sync"
-
- vsync "veyron.io/veyron/veyron/runtimes/google/lib/sync"
-)
-
-type fsNotifyWatcher struct {
- filename string
- source *fsnotify.Watcher
- // cancel signals Wait to terminate.
- cancel chan struct{}
- // pending allows Close to block till ongoing calls to Wait terminate.
- pending vsync.WaitGroup
- // mu and closed ensure that Close is idempotent.
- mu sync.Mutex
- closed bool // GUARDED_BY(mu)
-}
-
-// newFSNotifyWatcher returns an fsnotify-based fsWatcher.
-// Wait() blocks until it receives a file modification event from fsnotify.
-func newFSNotifyWatcher(filename string) (fsWatcher, error) {
- source, err := fsnotify.NewWatcher()
- if err != nil {
- return nil, err
- }
- if err := source.Watch(filename); err != nil {
- source.Close()
- return nil, err
- }
- return &fsNotifyWatcher{
- source: source,
- cancel: make(chan struct{}),
- }, nil
-}
-
-func (w *fsNotifyWatcher) Wait() error {
- // After Close returns, any call to Wait must return io.EOF.
- if !w.pending.TryAdd() {
- return io.EOF
- }
- defer w.pending.Done()
-
- for {
- select {
- case event := <-w.source.Event:
- if event.IsModify() {
- // Drain the event queue.
- drained := false
- for !drained {
- select {
- case <-w.source.Event:
- default:
- drained = true
- }
- }
- return nil
- }
- return fmt.Errorf("Unexpected event %v", event)
- case err := <-w.source.Error:
- return err
- case <-w.cancel:
- // After Close returns, any call to Wait must return io.EOF.
- return io.EOF
- }
- }
-}
-
-func (w *fsNotifyWatcher) Close() error {
- w.mu.Lock()
- defer w.mu.Unlock()
- if w.closed {
- return nil
- }
- w.closed = true
- close(w.cancel)
- w.pending.Wait()
- return w.source.Close()
-}
diff --git a/runtimes/google/lib/follow/notify_watcher_test.go b/runtimes/google/lib/follow/notify_watcher_test.go
deleted file mode 100644
index 8ee84a5..0000000
--- a/runtimes/google/lib/follow/notify_watcher_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// +build darwin freebsd linux netbsd openbsd windows
-
-package follow
-
-import (
- "io/ioutil"
- "os"
- "testing"
- "time"
-)
-
-func TestModificationNotify(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.modification.notify")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- defer testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- watcher, err := newFSNotifyWatcher(testFileName)
- if err != nil {
- t.Fatalf("newCustomFSWatcer() failed: %v", err)
- }
- timeout := time.Second
- if err := testModification(testFile, watcher, timeout); err != nil {
- t.Fatalf("testModification() failed: %v", err)
- }
-}
diff --git a/runtimes/google/lib/follow/other_config.go b/runtimes/google/lib/follow/other_config.go
deleted file mode 100644
index 8feb648..0000000
--- a/runtimes/google/lib/follow/other_config.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build !darwin,!freebsd,!linux,!netbsd,!openbsd,!windows
-
-package follow
-
-// newFSWatcher starts and returns a new os.Stat()-based fsWatcher.
-// filename specifies the file to watch.
-func newFSWatcher(filename string) (fsWatcher, error) {
- return newFSStatWatcher(filename)
-}
diff --git a/runtimes/google/lib/follow/reader.go b/runtimes/google/lib/follow/reader.go
deleted file mode 100644
index ab488cd..0000000
--- a/runtimes/google/lib/follow/reader.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package follow
-
-import (
- "io"
- "os"
- "sync"
-)
-
-// fsReader is an implementation of io.ReadCloser that reads synchronously
-// from a file, blocking until at least one byte is written to the file and is
-// available for reading.
-type fsReader struct {
- mu sync.Mutex
- // The file to read.
- file *os.File // GUARDED_BY(mu)
- // The watcher of modifications to the file.
- watcher fsWatcher
- // True if the reader is open for reading, false otherwise.
- closed bool // GUARDED_BY(mu)
-}
-
-// NewReader creates a new reader that reads synchronously from a file,
-// blocking until at least one byte is written to the file and is available
-// for reading.
-// The returned io.ReadCloser supports limited concurrency:
-// 1) Reads may not be called concurrently.
-// 2) Close may be called concurrently with Read, and will terminate Read.
-func NewReader(filename string) (reader io.ReadCloser, err error) {
- var file *os.File
- var watcher fsWatcher
- defer func() {
- if err == nil {
- return
- }
- var closeFileErr, closeWatcherErr error
- if file != nil {
- closeFileErr = file.Close()
- }
- if watcher != nil {
- closeWatcherErr = watcher.Close()
- }
- err = composeErrors(err, closeFileErr, closeWatcherErr)
- }()
- file, err = os.Open(filename)
- if err != nil {
- return nil, err
- }
- watcher, err = newFSWatcher(filename)
- if err != nil {
- return nil, err
- }
- return newCustomReader(file, watcher)
-}
-
-func newCustomReader(file *os.File, watcher fsWatcher) (io.ReadCloser, error) {
- reader := &fsReader{
- file: file,
- watcher: watcher,
- }
- return reader, nil
-}
-
-func (r *fsReader) Read(p []byte) (int, error) {
- // If the reader has been closed, return an error.
- r.mu.Lock()
- if r.closed {
- return 0, io.EOF
- }
-
- for {
- // Read any bytes that are available.
- if n, err := r.file.Read(p); err != io.EOF {
- r.mu.Unlock()
- return n, err
- }
- r.mu.Unlock()
-
- // Wait until the file is modified one or more times. The new
- // bytes from each corresponding modification have been
- // written to the file already, and therefore won't be skipped.
- if err := r.watcher.Wait(); err != nil {
- return 0, err
- }
-
- r.mu.Lock()
- }
-}
-
-// Close closes the reader synchronously.
-// 1) Terminates ongoing reads. (reads return io.EOF)
-// 2) Prevents future reads. (reads return io.EOF)
-// 3) Frees system resources associated with the reader.
-// Close is idempotent.
-func (r *fsReader) Close() error {
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.closed {
- return nil
- }
- // Mark the reader closed.
- r.closed = true
- // Release resources.
- closeFileErr := r.file.Close()
- closeWatcherErr := r.watcher.Close()
- return composeErrors(closeFileErr, closeWatcherErr)
-}
diff --git a/runtimes/google/lib/follow/stat_reader_test.go b/runtimes/google/lib/follow/stat_reader_test.go
deleted file mode 100644
index 21adb53..0000000
--- a/runtimes/google/lib/follow/stat_reader_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package follow
-
-import (
- "io/ioutil"
- "os"
- "testing"
- "time"
-)
-
-// TestStatReadPartial tests partial reads with the os.Stat()-based fsReader
-func TestStatReadPartial(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.stat.partial")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the os.Stat()-based fsWatcher.
- minSleep := 10 * time.Millisecond
- maxSleep := 100 * time.Millisecond
- watcher, err := newCustomFSStatWatcher(testFileName, minSleep, maxSleep)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testReadPartial(testFileName, watcher, timeout); err != nil {
- t.Fatalf("testReadPartial() failed: %v", err)
- }
-}
-
-// TestStatReadFull tests full reads with the os.Stat()-based fsReader
-func TestStatReadFull(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.stat.full")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the os.Stat()-based fsWatcher.
- minSleep := 10 * time.Millisecond
- maxSleep := 100 * time.Millisecond
- watcher, err := newCustomFSStatWatcher(testFileName, minSleep, maxSleep)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testReadFull(testFileName, watcher, timeout); err != nil {
- t.Fatalf("testReadFull() failed: %v", err)
- }
-}
-
-// TestStatClose tests close with the os.Stat()-based fsReader
-func TestStatClose(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.reader.stat.close")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- // Create the os.Stat()-based fsWatcher.
- minSleep := 10 * time.Millisecond
- maxSleep := 100 * time.Millisecond
- watcher, err := newCustomFSStatWatcher(testFileName, minSleep, maxSleep)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed: %v", err)
- }
-
- timeout := time.Second
- if err := testClose(testFileName, watcher, timeout); err != nil {
- t.Fatalf("testClose() failed: %v", err)
- }
-}
diff --git a/runtimes/google/lib/follow/stat_watcher.go b/runtimes/google/lib/follow/stat_watcher.go
deleted file mode 100644
index 6c288f8..0000000
--- a/runtimes/google/lib/follow/stat_watcher.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package follow
-
-import (
- "io"
- "os"
- "sync"
- "time"
-
- vsync "veyron.io/veyron/veyron/runtimes/google/lib/sync"
-)
-
-const (
- defaultMinSleep = 10 * time.Millisecond
- defaultMaxSleep = 5 * time.Second
-)
-
-type fsStatWatcher struct {
- minSleep time.Duration
- maxSleep time.Duration
- file *os.File
- lastFileSize int64
- // cancel signals Wait to terminate.
- cancel chan struct{}
- // pending allows Close to block till ongoing calls to Wait terminate.
- pending vsync.WaitGroup
- // mu and closed ensure that Close is idempotent.
- mu sync.Mutex
- closed bool // GUARDED_BY(mu)
-}
-
-// newFSStatWatcher returns an fsWatcher that polls os.Stat(), observing file
-// size. If the file size is larger than the previously-recorded file size,
-// the watcher assumes the file has been modified.
-// Wait() polls os.Stat() at an interval specified by minSleep, doubling that
-// interval as long the file is not modified, upto a maximum interval specified
-// by maxSleep. This allows faster detection during periods of frequent
-// modification but conserves resources during periods of inactivity.
-// The default values of minSleep and maxSleep can be overriden using the
-// newCustomFSStatWatcher() constructor.
-func newFSStatWatcher(filename string) (fsWatcher, error) {
- return newCustomFSStatWatcher(filename, defaultMinSleep, defaultMaxSleep)
-}
-
-func newCustomFSStatWatcher(filename string, minSleep, maxSleep time.Duration) (fsWatcher, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- fileInfo, err := file.Stat()
- if err != nil {
- file.Close()
- return nil, err
- }
- return &fsStatWatcher{
- minSleep: minSleep,
- maxSleep: maxSleep,
- file: file,
- lastFileSize: fileInfo.Size(),
- cancel: make(chan struct{}),
- }, nil
-}
-
-func (w *fsStatWatcher) Wait() error {
- // After Close returns, any call to Wait must return io.EOF.
- if !w.pending.TryAdd() {
- return io.EOF
- }
- defer w.pending.Done()
-
- sleep := w.minSleep
- for {
- select {
- case <-w.cancel:
- // After Close returns, any call to Wait must return io.EOF.
- return io.EOF
- default:
- }
- fileInfo, err := w.file.Stat()
- if err != nil {
- return err
- } else if fileSize := fileInfo.Size(); w.lastFileSize < fileSize {
- w.lastFileSize = fileSize
- return nil
- }
- time.Sleep(sleep)
- sleep = minDuration(sleep*2, w.maxSleep)
- }
-}
-
-func (w *fsStatWatcher) Close() error {
- w.mu.Lock()
- defer w.mu.Unlock()
- if w.closed {
- return nil
- }
- w.closed = true
- close(w.cancel)
- w.pending.Wait()
- return w.file.Close()
-}
diff --git a/runtimes/google/lib/follow/stat_watcher_test.go b/runtimes/google/lib/follow/stat_watcher_test.go
deleted file mode 100644
index 67d14e8..0000000
--- a/runtimes/google/lib/follow/stat_watcher_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package follow
-
-import (
- "io/ioutil"
- "os"
- "testing"
- "time"
-)
-
-func TestModificationStat(t *testing.T) {
- // Create the test file.
- testFile, err := ioutil.TempFile(os.TempDir(), "follow.modification.stat")
- if err != nil {
- t.Fatalf("ioutil.TempFile() failed: %v", err)
- }
- defer testFile.Close()
- testFileName := testFile.Name()
- defer os.Remove(testFileName)
-
- minSleep := 10 * time.Millisecond
- maxSleep := 100 * time.Millisecond
- watcher, err := newCustomFSStatWatcher(testFileName, minSleep, maxSleep)
- if err != nil {
- t.Fatalf("newCustomFSWatcher() failed : %v", err)
- }
- timeout := 100 * time.Millisecond
- if err := testModification(testFile, watcher, timeout); err != nil {
- t.Fatalf("testModification() failed: %v", err)
- }
-}
diff --git a/runtimes/google/lib/follow/test_util.go b/runtimes/google/lib/follow/test_util.go
deleted file mode 100644
index 0da3fac..0000000
--- a/runtimes/google/lib/follow/test_util.go
+++ /dev/null
@@ -1,279 +0,0 @@
-package follow
-
-import (
- "errors"
- "fmt"
- "io"
- "os"
- "time"
-)
-
-var (
- errTimedOut = errors.New("timed out")
- errCantAppend = errors.New("cannot append string to file")
- errUnexpectedModification = errors.New("unexpected modification event")
-)
-
-func events(watcher fsWatcher) <-chan error {
- events := make(chan error)
- go func() {
- for {
- event := watcher.Wait()
- events <- event
- if event == io.EOF {
- break
- }
- }
- }()
- return events
-}
-
-// readString reads a string of the specified length from the reader.
-// Returns an error if:
-// (A) reader.Read returned an error
-// (A) reader.Read timed out
-func readString(reader io.Reader, length int, timeout time.Duration) (string, error) {
- p := make([]byte, length, length)
-
- c := make(chan string)
- e := make(chan error)
- go func() {
- n, err := reader.Read(p)
- if err != nil {
- e <- err
- return
- }
- c <- string(p[:n])
- }()
-
- timer := time.After(timeout)
- select {
- case err := <-e:
- return "", err
- case s := <-c:
- return s, nil
- case <-timer:
- return "", errTimedOut
- }
-}
-
-// writeString appends a string to a file, and flushes its new contents to
-// stable storage.
-func writeString(file *os.File, s string) error {
- n, err := io.WriteString(file, s)
- if err != nil {
- return errors.New(fmt.Sprintf("io.WriteString() failed: %v", err))
- }
- if n < len(s) {
- return errCantAppend
- }
- file.Sync()
- return nil
-}
-
-// expectSilence tests that no events are received on the events channel
-// within the duration specified by timeout.
-func expectSilence(events <-chan error, timeout time.Duration) error {
- timer := time.After(timeout)
- select {
- case err := <-events:
- if err != nil {
- return err
- }
- return errUnexpectedModification
- case <-timer:
- // all's well
- return nil
- }
-}
-
-// expectModification tests that a modification event is received on the events
-// channel within the duration specified by timeout.
-func expectModification(events <-chan error, timeout time.Duration) error {
- timer := time.After(timeout)
- select {
- case <-timer:
- return errTimedOut
- case err := <-events:
- if err != nil {
- return err
- }
- // all's well
- return nil
- }
-}
-
-// testModification tests that the watcher sends events when the file is
-// modified.
-func testModification(file *os.File, watcher fsWatcher, timeout time.Duration) error {
- events := events(watcher)
- // no modifications, expect no events.
- if err := expectSilence(events, timeout); err != nil {
- return errors.New(fmt.Sprintf("expectSilence() failed with no modifications: %v ", err))
- }
- // modify once, expect event.
- if err := writeString(file, "modification one"); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed on modification one: %v ", err))
- }
- if err := expectModification(events, timeout); err != nil {
- return errors.New(fmt.Sprintf("expectModication() failed on modification one: %v ", err))
- }
- // no further modifications, expect no events.
- if err := expectSilence(events, timeout); err != nil {
- return errors.New(fmt.Sprintf("expectSilence() failed after modification one: %v ", err))
- }
- // modify again, expect event.
- if err := writeString(file, "modification two"); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed on modification two: %v ", err))
- }
- if err := expectModification(events, time.Hour); err != nil {
- return errors.New(fmt.Sprintf("expectModification() failed on modification two: %v ", err))
- }
- // no further modifications, expect no events.
- if err := expectSilence(events, timeout); err != nil {
- return errors.New(fmt.Sprintf("expectSilence() failed after modification two: %v ", err))
- }
- return nil
-}
-
-// testClose tests the implementation of fsReader.Read(). Specifically,
-// tests that Read() blocks if the requested bytes are not available for
-// reading in the underlying file.
-func testReadPartial(testFileName string, watcher fsWatcher, timeout time.Duration) error {
- s0 := "part"
-
- // Open the file for writing.
- testfileW, err := os.OpenFile(testFileName, os.O_WRONLY, 0)
- if err != nil {
- return errors.New(fmt.Sprintf("os.OpenFile() failed: %v", err))
- }
- defer testfileW.Close()
-
- // Open the file for reading.
- testfileR, err := os.Open(testFileName)
- if err != nil {
- return errors.New(fmt.Sprintf("os.Open() failed: %v", err))
- }
- // Create the reader.
- reader, err := newCustomReader(testfileR, watcher)
- if err != nil {
- return errors.New(fmt.Sprintf("newCustomReader() failed: %v", err))
- }
- defer reader.Close()
-
- // Write a part of the string.
- if err := writeString(testfileW, s0); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed: %v ", err))
- }
-
- // Some bytes written, but not enough to fill the buffer. Read should
- // still succeed.
- s, err := readString(reader, len(s0)+1, timeout)
- if s != s0 {
- return errors.New(fmt.Sprintf("Expected to read: %v, but read: %v", s0, s))
- }
- // No more bytes written, so read should block.
- s, err = readString(reader, len(s0)+1, timeout)
- if err != errTimedOut {
- return errors.New(fmt.Sprintf("readString() failed, expected timeout: %v", err))
- }
- if s != "" {
- return errors.New(fmt.Sprintf("Did not expect to read: %v", s))
- }
-
- return nil
-}
-
-// testClose tests the implementation of fsReader.Read(). Specifically,
-// tests that Read() returns the requested bytes when they are available
-// for reading in the underlying file.
-func testReadFull(testFileName string, watcher fsWatcher, timeout time.Duration) error {
- s0, s1, s2 := "partitioned", "parti", "tioned"
-
- // Open the file for writing.
- testfileW, err := os.OpenFile(testFileName, os.O_WRONLY, 0)
- if err != nil {
- return errors.New(fmt.Sprintf("os.Open() failed: %v", err))
- }
- defer testfileW.Close()
-
- // Open the file for reading.
- testfileR, err := os.Open(testFileName)
- if err != nil {
- return errors.New(fmt.Sprintf("os.Open() failed: %v", err))
- }
- // Create the reader.
- reader, err := newCustomReader(testfileR, watcher)
- if err != nil {
- return errors.New(fmt.Sprintf("newCustomReader() failed: %v", err))
- }
- defer reader.Close()
-
- // Write part one of the string.
- if err := writeString(testfileW, s1); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed on part one: %v ", err))
- }
-
- // Write part two of the string.
- if err := writeString(testfileW, s2); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed on part two: %v ", err))
- }
-
- // Enough bytes written, so read should succeed.
- s, err := readString(reader, len(s0), timeout)
- if err != nil {
- return errors.New(fmt.Sprintf("readString() failed: %v", err))
- }
- if s != s0 {
- return errors.New(fmt.Sprintf("Expected to read: %v, but read: %v", s0, s))
- }
-
- return nil
-}
-
-// testClose tests the implementation of fsReader.Close()
-func testClose(testFileName string, watcher fsWatcher, timeout time.Duration) error {
- s := "word"
-
- // Open the file for writing.
- testfileW, err := os.OpenFile(testFileName, os.O_WRONLY, 0)
- if err != nil {
- return errors.New(fmt.Sprintf("os.OpenFile() failed: %v", err))
- }
- defer testfileW.Close()
-
- // Open the file for reading.
- testfileR, err := os.Open(testFileName)
- if err != nil {
- return errors.New(fmt.Sprintf("os.Open() failed: %v", err))
- }
- // Create the reader.
- reader, err := newCustomReader(testfileR, watcher)
- if err != nil {
- return errors.New(fmt.Sprintf("newCustomReader() failed: %v", err))
- }
-
- // Close the reader.
- if err := reader.Close(); err != nil {
- return errors.New(fmt.Sprintf("Close() failed: %v", err))
- }
-
- // Close the reader again.
- if err := reader.Close(); err != nil {
- return errors.New(fmt.Sprintf("Duplicate Close() failed: %v", err))
- }
-
- // Write the string.
- if err := writeString(testfileW, s); err != nil {
- return errors.New(fmt.Sprintf("writeString() failed: %v ", err))
- }
-
- // Reader is closed, readString() should fail.
- if _, err := readString(reader, len(s), timeout); err == nil {
- return errors.New("Expected readString() to fail")
- } else if err != io.EOF {
- return errors.New(fmt.Sprintf("readString() failed with unexpected error: %v", err))
- }
-
- return nil
-}
diff --git a/runtimes/google/lib/follow/util.go b/runtimes/google/lib/follow/util.go
deleted file mode 100644
index a44d6b0..0000000
--- a/runtimes/google/lib/follow/util.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package follow
-
-import (
- "bytes"
- "errors"
- "fmt"
- "time"
-)
-
-// composeErrors composes several (some nil) errors, returning a single error:
-// (A) If the input errors are all nil, nil is returned.
-// (B) If exactly one error is non-nil, that error is returned.
-// (C) Otherwise, a composite error is returned. The composite error message
-// lists all non-nil individual error messages. However, the composite
-// error does not provide direct access to individual errors.
-func composeErrors(errs ...error) error {
- // n is the number of non-nil errors.
- n := 0
- // lastErr is the last-seen non-nil error.
- var lastErr error
- for _, err := range errs {
- if err != nil {
- n++
- lastErr = err
- }
- }
- // all input errors are nil, return nil.
- if n == 0 {
- return nil
- }
- // exactly one error is non-nil, return it.
- if n == 1 {
- return lastErr
- }
- // more than one error is non-nil, build a composite error message.
- var msgBuffer bytes.Buffer
- fmt.Fprintf(&msgBuffer, "Encountered %v errors:\n", n)
- i := 0
- for _, err := range errs {
- if err != nil {
- i++
- fmt.Fprintf(&msgBuffer, "error %d: %v:\n", i, err)
- }
- }
- return errors.New(msgBuffer.String())
-}
-
-// minDuration takes two durations and returns the shorter.
-func minDuration(d1, d2 time.Duration) time.Duration {
- if d2 < d1 {
- return d2
- }
- return d1
-}
diff --git a/runtimes/google/lib/follow/util_test.go b/runtimes/google/lib/follow/util_test.go
deleted file mode 100644
index 653dacd..0000000
--- a/runtimes/google/lib/follow/util_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package follow
-
-import (
- "errors"
- "strings"
- "testing"
-)
-
-// TestComposeErrors tests composeErrors() with combinations of nil and non-nil inputs
-func TestComposeErrors(t *testing.T) {
- // no errors compose to a nil error.
- if err := composeErrors(); err != nil {
- t.Fatalf("Expected composeErrors() to return nil, not: %v", err)
- }
- // a single nil error composes to a nil error.
- if err := composeErrors(nil); err != nil {
- t.Fatalf("Expected composeErrors(nil) to return nil, not: %v", err)
- }
- // multiple nil errors compose to a nil error.
- if err := composeErrors(nil, nil); err != nil {
- t.Fatalf("Expected composeErrors(nil, nil) to return nil, not: %v", err)
- }
- // a single non-nil error composes to itself.
- err1 := errors.New("err1")
- if err := composeErrors(err1); err != err1 {
- t.Fatalf("Expected composeErrors(err) to return %v, not: %v", err1, err)
- }
- if err := composeErrors(nil, err1); err != err1 {
- t.Fatalf("Expected composeErrors(nil, err) to return %v, not: %v", err1, err)
- }
- if err := composeErrors(err1, nil); err != err1 {
- t.Fatalf("Expected composeErrors(err, nil) to return %v, not: %v", err1, err)
- }
- // multiple non-nil errors compose to composite error.
- err2 := errors.New("err2")
- err := composeErrors(err1, err2)
- if err == nil {
- t.Fatal("Expected composeErrors(err1, err2) to be non-nil")
- }
- if !strings.Contains(err.Error(), err1.Error()) {
- t.Fatalf("Expected composeErrors(err1, err2) to contain %v, but was: %v", err1, err)
- }
- if !strings.Contains(err.Error(), err2.Error()) {
- t.Fatalf("Expected composeErrors(err1, err2) to contain %v, but was: %v", err2, err)
- }
- // multiple non-nil errors compose to composite error without nil errors.
- err = composeErrors(err1, err2, nil)
- if err == nil {
- t.Fatal("Expected composeErrors(err1, err2, nil) to be non-nil")
- }
- if !strings.Contains(err.Error(), err1.Error()) {
- t.Fatalf("Expected composeErrors(err1, err2, nil) to contain %v, but was: %v", err1, err)
- }
- if !strings.Contains(err.Error(), err2.Error()) {
- t.Fatalf("Expected composeErrors(err1, err2, nil) to contain %v, but was: %v", err2, err)
- }
-}
diff --git a/runtimes/google/lib/follow/watcher.go b/runtimes/google/lib/follow/watcher.go
deleted file mode 100644
index d5ad863..0000000
--- a/runtimes/google/lib/follow/watcher.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package follow
-
-// fsWatcher is a tool for watching append-only modifications to a file.
-type fsWatcher interface {
- // Wait blocks until the file is modified.
- // Wait returns an io.EOF if the watcher is closed, and immediately returns
- // any error it encounters while blocking.
- Wait() error
-
- // Close closes the watcher synchronously. Any ongoing or following calls
- // to Wait return io.EOF.
- // Close is idempotent.
- Close() error
-}
diff --git a/runtimes/google/lib/functional/op/operations.go b/runtimes/google/lib/functional/op/operations.go
deleted file mode 100644
index 9e999a9..0000000
--- a/runtimes/google/lib/functional/op/operations.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package op
-
-import (
- "veyron.io/veyron/veyron/runtimes/google/lib/functional"
-)
-
-// IterDifference iterates through the difference of two sets, applying function f
-// to each element that belongs to s1 but not s2. IterDifference terminates when
-// all elements have been examined, or when the function returns false.
-func IterDifference(s1, s2 functional.Set, f func(it interface{}) bool) {
- // TODO(tilaks): if s1 and s2 use the same comparator, iterate s1 and s2
- // by merging their iterators in O(len(s1) + len(s2)) steps. This is difficult
- // because comparators are functions, and not comparable. It is, however,
- // reasonable to iterate s2 and detect inversions under s1's comparator.
- s1.Iter(func(it interface{}) bool {
- if s2.Contains(it) {
- return true
- }
- return f(it)
- })
-}
diff --git a/runtimes/google/lib/functional/op/operations_test.go b/runtimes/google/lib/functional/op/operations_test.go
deleted file mode 100644
index 2d49c46..0000000
--- a/runtimes/google/lib/functional/op/operations_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package op
-
-import (
- "testing"
-
- "veyron.io/veyron/veyron/runtimes/google/lib/functional"
- "veyron.io/veyron/veyron/runtimes/google/lib/functional/rb"
-)
-
-func intCompare(it1, it2 interface{}) bool {
- return it1.(int) < it2.(int)
-}
-
-func difference(s1, s2 functional.Set) functional.Set {
- difference := rb.NewSet(intCompare)
- IterDifference(s1, s2, func(it interface{}) bool {
- difference = difference.Put(it)
- return true
- })
- return difference
-}
-
-func TestDifferenceEmpty(t *testing.T) {
- s1 := rb.NewSet(intCompare)
- s2 := rb.NewSet(intCompare)
- difference := difference(s1, s2)
- if !difference.IsEmpty() {
- t.Fatal("Difference is not empty")
- }
-}
-
-func TestDifferenceDisjoint(t *testing.T) {
- s1 := rb.NewSet(intCompare).Put(1).Put(2).Put(3)
- s2 := rb.NewSet(intCompare).Put(4).Put(5)
- difference := difference(s1, s2)
- if difference.Len() != 3 {
- t.Fatalf("Difference does not contain 3 elements ")
- }
- for i := 1; i <= 3; i++ {
- if !difference.Contains(i) {
- t.Fatalf("Difference does not contain %v", i)
- }
- }
-}
-
-func TestDifferenceOverlapping(t *testing.T) {
- s1 := rb.NewSet(intCompare).Put(1).Put(2).Put(3).Put(5)
- s2 := rb.NewSet(intCompare).Put(4).Put(5)
- difference := difference(s1, s2)
- if difference.Len() != 3 {
- t.Fatalf("Difference does not contain 3 elements ")
- }
- for i := 1; i <= 3; i++ {
- if !difference.Contains(i) {
- t.Fatalf("Difference does not contain %v", i)
- }
- }
-}
diff --git a/runtimes/google/lib/functional/rb/rb_set.go b/runtimes/google/lib/functional/rb/rb_set.go
deleted file mode 100644
index 04d924a..0000000
--- a/runtimes/google/lib/functional/rb/rb_set.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Package rb implements functional sets using red-black trees. This
-// implementation is based on Chris Okasaki's functional red-black trees.
-//
-// Article: Functional Pearls
-// Title: Red-Black Trees in a Functional Setting
-// Author: Chris Okasaki
-// J. Functional Programming, Jan 1993
-// http://www.cs.tufts.edu/~nr/cs257/archive/chris-okasaki/redblack99.ps‎
-//
-// Okasaki doesn't define deletion. This version of deletion is based on Stefan
-// Khar's untyped implementation in Haskell.
-//
-// Title: Red-black trees with types.
-// Author: Stefan Khars
-// J. Functional Programming, July 2001
-// http://www.cs.kent.ac.uk/people/staff/smk/redblack/Untyped.hs
-//
-// By "functional," we mean that functions behave like mathematical functions:
-// given the same argument, the function always returns the same value. The
-// practical consequence is that a set is immutable and persistent. It can't be
-// changed once it is constructed. The operations that mutate the set (Put and
-// Remove) return a new set instead.
-//
-// Red-black trees have the following invariants.
-// - Every node is colored either red or black.
-// - The root is black.
-// - RED: Red nodes have only black children.
-// - BLACK: All paths from the root to the leaves have the same number of black nodes.
-//
-// These invariants imply that the longest path to a leaf is at most twice as
-// long as the shortest path, hence the tree is balanced. Insertion and deletion
-// take O(log N) time, where N is the number of elements in the set.
-package rb
-
-import (
- "veyron.io/veyron/veyron/runtimes/google/lib/functional"
-)
-
-// Node colors are RED and BLACK
-type color int
-
-const (
- BLACK color = iota
- RED
-)
-
-// rbSet is the red-black set.
-type rbSet struct {
- root *node
- cmp functional.Comparator
- size int
-}
-
-// The tree is constructed from Node. Each Node has a color,
-// two children, and a label.
-type node struct {
- color color
- key interface{}
- left *node
- right *node
-}
-
-// iterator contains a path to a node.
-type iterator struct {
- path []*node
-}
-
-func newNode(color color, key interface{}, left, right *node) *node {
- return &node{color: color, key: key, left: left, right: right}
-}
-
-func isBlack(n *node) bool {
- return n == nil || n.color == BLACK
-}
-
-func isRed(n *node) bool {
- return n != nil && n.color == RED
-}
-
-func makeBlack(n *node) *node {
- if isBlack(n) {
- return n
- }
- return newNode(BLACK, n.key, n.left, n.right)
-}
-
-func makeRed(n *node) *node {
- if isRed(n) {
- panic("Node is already RED")
- }
- return newNode(RED, n.key, n.left, n.right)
-}
-
-// NewSet returns an empty set using the comparator.
-func NewSet(cmp functional.Comparator) functional.Set {
- return &rbSet{cmp: cmp}
-}
-
-func (s *rbSet) newSet(root *node, size int) *rbSet {
- return &rbSet{root: root, cmp: s.cmp, size: size}
-}
-
-func (s *rbSet) newSetWithCmp(root *node, cmp functional.Comparator, size int) *rbSet {
- return &rbSet{root: root, cmp: cmp, size: size}
-}
-
-// IsEmpty returns true iff the set is empty.
-func (s *rbSet) IsEmpty() bool {
- return s.root == nil
-}
-
-// Len returns the size of the tree.
-func (s *rbSet) Len() int {
- return s.size
-}
-
-// Contains returns true iff the element is in the set.
-func (s *rbSet) Contains(key interface{}) bool {
- for n := s.root; n != nil; {
- if s.cmp(key, n.key) {
- n = n.left
- } else if s.cmp(n.key, key) {
- n = n.right
- } else {
- return true
- }
- }
- return false
-}
-
-// Get returns the element in the s.
-func (s *rbSet) Get(key interface{}) (interface{}, bool) {
- for n := s.root; n != nil; {
- if s.cmp(key, n.key) {
- n = n.left
- } else if s.cmp(n.key, key) {
- n = n.right
- } else {
- return n.key, true
- }
- }
- return nil, false
-}
-
-// Put adds an element to the set, replacing any existing element.
-func (s *rbSet) Put(key interface{}) functional.Set {
- root, sizeChanged := insert(s.cmp, key, s.root)
- size := s.size
- if sizeChanged {
- size++
- }
- return s.newSet(makeBlack(root), size)
-}
-
-// insert performs the actual insertion, returning the new node, and a bool
-// indicating whether the tree size has changed.
-//
-// Tree balancing is performed bottom-up; the stratgey is for black grandparents
-// to fix red-parent + red-child violations by rotating the tree and recoloring.
-func insert(cmp functional.Comparator, key interface{}, n *node) (*node, bool) {
- switch {
- case n == nil:
- n = newNode(RED, key, nil, nil)
- return n, true
- case n.color == BLACK:
- switch {
- case cmp(key, n.key):
- left, sizeChanged := insert(cmp, key, n.left)
- if sizeChanged {
- n = balance(n.key, left, n.right)
- } else {
- n = newNode(BLACK, n.key, left, n.right)
- }
- return n, sizeChanged
- case cmp(n.key, key):
- right, sizeChanged := insert(cmp, key, n.right)
- if sizeChanged {
- n = balance(n.key, n.left, right)
- } else {
- n = newNode(BLACK, n.key, n.left, right)
- }
- return n, sizeChanged
- default:
- // key and n.key are in the same equivalence class according to cmp,
- // but we choose key as the representative.
- n = newNode(BLACK, key, n.left, n.right)
- return n, false
- }
- default: // n.color == RED
- switch {
- case cmp(key, n.key):
- left, sizeChanged := insert(cmp, key, n.left)
- n = newNode(RED, n.key, left, n.right)
- return n, sizeChanged
- case cmp(n.key, key):
- right, sizeChanged := insert(cmp, key, n.right)
- n = newNode(RED, n.key, n.left, right)
- return n, sizeChanged
- default:
- // key and n.key are in the same equivalence class according to cmp,
- // but we choose the new key as the representative.
- n = newNode(RED, key, n.left, n.right)
- return n, false
- }
- }
-}
-
-// Operations on the tree that insert and delete nodes may violate the
-// RED invariant. The purpose of the balance function is to restore
-// the RED invariant by rotating the tree whenever one of the
-// arguments has a RED root with a RED child. In addition, if both
-// arguments have a RED root, we migrate the RED to the root, and make
-// both subtrees BLACK.
-func balance(key interface{}, left, right *node) *node {
- if isRed(left) {
- switch {
- case isRed(right):
- // This is an optimization not in Okasaki's original algorithm, to
- // reduce the number of reds by migrating them upward.
- return newNode(RED, key,
- newNode(BLACK, left.key, left.left, left.right),
- newNode(BLACK, right.key, right.left, right.right))
- case isRed(left.left):
- return newNode(RED, left.key,
- newNode(BLACK, left.left.key, left.left.left, left.left.right),
- newNode(BLACK, key, left.right, right))
- case isRed(left.right):
- return newNode(RED, left.right.key,
- newNode(BLACK, left.key, left.left, left.right.left),
- newNode(BLACK, key, left.right.right, right))
- }
- }
- if isRed(right) {
- switch {
- case isRed(right.left):
- return newNode(RED, right.left.key,
- newNode(BLACK, key, left, right.left.left),
- newNode(BLACK, right.key, right.left.right, right.right))
- case isRed(right.right):
- return newNode(RED, right.key,
- newNode(BLACK, key, left, right.left),
- newNode(BLACK, right.right.key, right.right.left, right.right.right))
- }
- }
- return newNode(BLACK, key, left, right)
-}
-
-// Removes removes an element from the set.
-func (s *rbSet) Remove(key interface{}) functional.Set {
- root := remove(s.cmp, key, s.root)
- if root == s.root {
- return s // Nothing changed.
- }
- return s.newSet(makeBlack(root), s.size-1)
-}
-
-func remove(cmp functional.Comparator, key interface{}, n *node) *node {
- switch {
- case n == nil:
- return nil
- case cmp(key, n.key):
- left := remove(cmp, key, n.left)
- switch {
- case left == n.left:
- return n // Nothing changed.
- case isBlack(n.left):
- return balanceLeft(n.key, left, n.right)
- default:
- return newNode(RED, n.key, left, n.right)
- }
- case cmp(n.key, key):
- right := remove(cmp, key, n.right)
- switch {
- case right == n.right:
- return n // Nothing changed.
- case isBlack(n.right):
- return balanceRight(n.key, n.left, right)
- default:
- return newNode(RED, n.key, n.left, right)
- }
- default:
- // If either of n.left or n.right is RED, the result of concat() might
- // violate the RED invariant at the root. This can happen only if
- // n.color == BLACK. If so, the recursive calls to remove in this
- // function are followed by calls to balanceLeft or balanceRight to
- // rebalance the tree. See the balanceLeft comment below.
- return concat(n.left, n.right)
- }
-}
-
-// balanceLeft is called to balance a node after a deletion in the left branch.
-// The original node had key "key", "left" is the new left subtree after the
-// deletion was performed, and "right" is the original right child.
-//
-// REQUIRES: Before deletion, the left node was BLACK. Since we have deleted an
-// element from the left subtree, the black depth of "left" is now one less than
-// the black depth of "right".
-//
-// ALLOWED: The left subtree may violate the RED invariant, but only at the
-// root (left might be RED and also have a RED child, but otherwise it is
-// balanced).
-//
-// To preserve the BLACK invariant, we need to subtract a BLACK from the right
-// subtree, or else rotate the tree to preserve the invariant. In all cases, if
-// the original root node was BLACK, then the resulting tree has one less black
-// depth; if the original tree was RED, the resulting tree has the same black
-// depth.
-//
-// There are three cases.
-//
-// 1. If the new left child is RED, change the left child to BLACK and create
-// a RED root. If the original root node was BLACK, the total black depth
-// is decreased by one.
-//
-// 2. Otherwise, the left child is BLACK. If the right child is also BLACK,
-// make the right child RED. This may invalidate the RED invariant, so
-// call the Balance() method to fix it.
-//
-// 3. Otherwise, left is BLACK, right is RED, and the original root was BLACK.
-// Since the left tree is BLACK, the right node *must* have two BLACK
-// non-leaf children. Pick the left one, and rotate it into the left
-// subtree.
-func balanceLeft(key interface{}, left, right *node) *node {
- switch {
- case isRed(left):
- return newNode(RED, key, newNode(BLACK, left.key, left.left, left.right), right)
- case isBlack(right):
- return balance(key, left, makeRed(right))
- default: // isRed(right) && isBlack(right.left)
- return newNode(RED, right.left.key,
- newNode(BLACK, key, left, right.left.left),
- balance(right.key, right.left.right, makeRed(right.right)))
- }
-}
-
-func balanceRight(key interface{}, left, right *node) *node {
- switch {
- case isRed(right):
- return newNode(RED, key, left, newNode(BLACK, right.key, right.left, right.right))
- case isBlack(left):
- return balance(key, makeRed(left), right)
- default: // isRed(left) && isBlack(left.right)
- return newNode(RED, left.right.key,
- balance(left.key, makeRed(left.left), left.right.left),
- newNode(BLACK, key, left.right.right, right))
- }
-}
-
-// Append the left and right trees. The max of the left is smaller than the min
-// of the right, and the two trees have the same black depth.
-//
-// The BLACK invariant holds for the resulting tree, and it has the same black
-// depth as the input trees.
-//
-// If both arguments have BLACK roots, the RED invariant will hold for the
-// resulting tree. However, if either argument has a RED root, the RED
-// invariant may not hold for the result, which may be violated at the root
-// (only). In this case, the caller will need to rebalance the tree. See the
-// comments for the recursive calls below.
-func concat(left, right *node) *node {
- switch {
- case left == nil:
- return right
- case right == nil:
- return left
- case left.color == BLACK && right.color == BLACK:
- // middle has the same black depth as left.right and right.left.
- middle := concat(left.right, right.left)
- if isRed(middle) {
- // middle may have a RED violation at the root, but if it does, the
- // subtrees are balanced, so we can balance the result by giving
- // them BLACK parents.
- return newNode(RED, middle.key,
- newNode(BLACK, left.key, left.left, middle.left),
- newNode(BLACK, right.key, middle.right, right.right))
- } else {
- // left.left, middle, and right.right all have the same black depth,
- // middle is BLACK, and we're adding a new BLACK node. Use
- // balanceLeft to restore the BLACK invariant.
- return balanceLeft(left.key, left.left,
- newNode(BLACK, right.key, middle, right.right))
- }
- case left.color == RED && right.color == RED:
- // middle has the same black depth as left.right and right.left.
- middle := concat(left.right, right.left)
- if isRed(middle) {
- // All parts, left.left, middle.left, middle.right, and right.right,
- // have the same black depth, and they are all BLACK. To preserve
- // the black depth, use only RED modes, violating the RED invariant.
- return newNode(RED, middle.key,
- newNode(RED, left.key, left.left, middle.left),
- newNode(RED, right.key, middle.right, right.right))
- } else {
- // left.left, middle, and right.right all have the same black depth
- // and all are BLACK. To preserve the black depth, use only RED
- // modes, violating the RED invariant.
- return newNode(RED, left.key, left.left,
- newNode(RED, right.key, middle, right.right))
- }
- case left.color == RED: // right.color == BLACK
- // left.left, left.right, and right have the same black depth. To preserve
- // the black depth, return a RED node, possibly violating the RED invariant.
- return newNode(RED, left.key, left.left, concat(left.right, right))
- default: // left.color == BLACK && right.color == RED
- // left, right.left, and right.right have the same black depth. To
- // preserve the black depth, return a RED node, possibly violating the
- // RED invariant.
- return newNode(RED, right.key, concat(left, right.left), right.right)
- }
-}
-
-// Iter applies a function to each element of the set in order. The iteration
-// terminates if the function returns false.
-func (s *rbSet) Iter(f func(it interface{}) bool) {
- if s.root != nil {
- s.root.iter(f)
- }
-}
-
-func (n *node) iter(f func(it interface{}) bool) {
- if n.left != nil {
- n.left.iter(f)
- }
- f(n.key)
- if n.right != nil {
- n.right.iter(f)
- }
-}
-
-// Map applies a function to each element of the set in order, replacing the
-// elements value. The ordering of the elements must be preserved.
-//
-// { e1, e2, ..., eN }.Map(f) = { f(e1), f(e2), ..., f(eN) }
-//
-// Typically, this is use in dictionaries to update the value part of a
-// key/value pair, keeping the key unchanged, so preserving the order.
-func (s *rbSet) Map(f func(it interface{}) interface{}, cmp functional.Comparator) functional.Set {
- if s.root == nil {
- return s
- }
- return s.newSetWithCmp(s.root.fmap(f), cmp, s.size)
-}
-
-func (n *node) fmap(f func(it interface{}) interface{}) *node {
- left := n.left
- if left != nil {
- left = left.fmap(f)
- }
- key := f(n.key)
- right := n.right
- if right != nil {
- right = right.fmap(f)
- }
- return newNode(n.color, key, left, right)
-}
-
-// Fold applies a function to the elements of the set in order, accumulating the
-// results.
-//
-// { e1, e2, ..., eN }.Fold(f, x) = f(eN, ... f(e2, f(e1, x)))
-//
-// For example, a sum could be computed as follows.
-//
-// s.Fold(func (it, accum interface{}) interface{} {
-// return it.(int) + accum.(int)
-// })
-//
-func (s *rbSet) Fold(f func(it, x interface{}) interface{}, x interface{}) interface{} {
- if s.root == nil {
- return x
- }
- return s.root.fold(f, x)
-}
-
-func (n *node) fold(f func(it, x interface{}) interface{}, x interface{}) interface{} {
- if n.left != nil {
- x = n.left.fold(f, x)
- }
- x = f(n.key, x)
- if n.right != nil {
- x = n.right.fold(f, x)
- }
- return x
-}
-
-// iteration.
-func (s *rbSet) Iterator() functional.Iterator {
- if s.root == nil {
- return &iterator{}
- }
- var path []*node
- for node := s.root; node != nil; node = node.left {
- path = append(path, node)
- }
- return &iterator{path: path}
-}
-
-// IsValid returns true iff the iterator refers to an element.
-func (it *iterator) IsValid() bool {
- return len(it.path) != 0
-}
-
-// Get returns the current element.
-func (it *iterator) Get() interface{} {
- i := len(it.path)
- if i == 0 {
- return nil
- }
- return it.path[i-1].key
-}
-
-// Next advances to the next element.
-func (it *iterator) Next() {
- path := it.path
- i := len(path)
- if i == 0 {
- return
- }
- current := path[i-1]
-
- // If there is a right child, descend to leftmost leaf.
- if current.right != nil {
- for node := current.right; node != nil; node = node.left {
- path = append(path, node)
- }
- it.path = path
- return
- }
-
- // If there is no right child, ascend to the nearest ancestor for which the
- // path branches left. That's the next in-order element.
- for j := i - 2; j >= 0; j-- {
- parent := path[j]
- if current == parent.left {
- // Found a left branch.
- it.path = path[:j+1]
- return
- }
- current = parent
- }
-
- // This was the rightmost path; we're done.
- it.path = nil
-}
diff --git a/runtimes/google/lib/functional/rb/rb_set_test.go b/runtimes/google/lib/functional/rb/rb_set_test.go
deleted file mode 100644
index 45eee44..0000000
--- a/runtimes/google/lib/functional/rb/rb_set_test.go
+++ /dev/null
@@ -1,426 +0,0 @@
-package rb
-
-import (
- "fmt"
- "log"
- "testing"
-
- "veyron.io/veyron/veyron/lib/testutil"
- "veyron.io/veyron/veyron/runtimes/google/lib/functional"
-)
-
-func init() { testutil.Init() }
-
-const (
- // Number of elements to check
- kMaxElement = 100
-
- // Number of times to loop during the random test
- kLoopCount = 10000
-)
-
-// Invariant checking.
-func checkInvariants(t *testing.T, s functional.Set) {
- set := s.(*rbSet)
- b := checkRedInvariant(t, set.root) &&
- checkRootInvariant(t, set.root) &&
- checkBlackInvariant(t, set.root)
- if !b {
- drawTree("", set.root)
- }
-}
-
-func checkRedInvariant(t *testing.T, node *node) bool {
- if node == nil {
- return true
- }
- if node.color == RED && (node.left != nil && node.left.color == RED ||
- node.right != nil && node.right.color == RED) {
- t.Errorf("RedBlackSet: RED invariant violation")
- return false
- }
- return checkRedInvariant(t, node.left) && checkRedInvariant(t, node.right)
-}
-
-func checkRootInvariant(t *testing.T, root *node) bool {
- if root != nil && root.color != BLACK {
- t.Errorf("RedBlackSet: Root is not BLACK")
- return false
- }
- return true
-}
-
-func checkBlackInvariant(t *testing.T, root *node) bool {
- depth := 0
- for node := root; node != nil; node = node.left {
- if node.color == BLACK {
- depth++
- }
- }
- b := checkBlackPathInvariant(depth, root)
- if !b {
- t.Error("Not all paths have the same number of black nodes")
- }
- return b
-}
-
-func checkBlackPathInvariant(expected int, node *node) bool {
- if node == nil {
- return expected == 0
- }
- if node.color == BLACK {
- expected--
- }
- return checkBlackPathInvariant(expected, node.left) && checkBlackPathInvariant(expected, node.right)
-}
-
-func (set *rbSet) printTree() {
- drawTree("x", set.root)
-}
-
-func drawTree(prefix string, node *node) {
- if node != nil {
- var color string
- if node.color == RED {
- color = "R"
- } else {
- color = "B"
- }
- log.Printf("%s %s %d", prefix, color, node.key)
- drawTree(prefix+"l", node.left)
- drawTree(prefix+"r", node.right)
- }
-}
-
-func intCompare(it1, it2 interface{}) bool {
- return it1.(int) < it2.(int)
-}
-
-func checkMembership(t *testing.T, s functional.Set, elements map[int]struct{}) {
- checkInvariants(t, s)
-
- s.Iter(func(it interface{}) bool {
- i := it.(int)
- if _, ok := elements[i]; !ok {
- t.Errorf("Extra element: %d", i)
- }
- return true
- })
- itElements := make(map[int]struct{})
- for it := s.Iterator(); it.IsValid(); it.Next() {
- i := it.Get().(int)
- if _, ok := elements[i]; !ok {
- t.Errorf("Extra element: %d", i)
- }
- itElements[i] = struct{}{}
- }
- for i, _ := range elements {
- if !s.Contains(i) {
- t.Errorf("Missing element: %d", i)
- }
- if j, ok := s.Get(i); !ok || j != i {
- t.Errorf("Expected %d, got %d", i, j)
- }
- if _, ok := itElements[i]; !ok {
- t.Errorf("Missing iterator element: %d", i)
- }
- }
-
- if s.Len() != len(elements) {
- t.Errorf("Expected size %d, actual size %d", len(elements), s.Len())
- }
-}
-
-// Sequential add and remove elements.
-func TestAddRemove(t *testing.T) {
- s := NewSet(intCompare)
- if !s.IsEmpty() {
- t.Errorf("set should be empty: %v", s)
- }
- if s.Len() != 0 {
- t.Errorf("set should have size zero: %d", s.Len())
- }
-
- // Add some elements
- elements := make(map[int]struct{})
- for i := 0; i != kMaxElement; i++ {
- s = s.Put(i)
- elements[i] = struct{}{}
- checkMembership(t, s, elements)
- }
-
- // Remove some elements
- for i := kMaxElement - 1; i > 0; i-- {
- s = s.Remove(i)
- delete(elements, i)
- checkMembership(t, s, elements)
- }
-}
-
-// Randomized add and remove.
-func TestRandom(t *testing.T) {
- s := NewSet(intCompare)
- elements := make(map[int]struct{})
- for i := 0; i != kLoopCount; i++ {
- switch testutil.Rand.Intn(2) {
- case 0:
- // Insertion
- x := testutil.Rand.Intn(kMaxElement)
- elements[x] = struct{}{}
- s = s.Put(x)
- case 1:
- // Deletion
- x := testutil.Rand.Intn(kMaxElement)
- delete(elements, x)
- s = s.Remove(x)
- }
- checkMembership(t, s, elements)
- }
-}
-
-// Map test.
-type entry struct {
- key, value int
-}
-
-func entryLessThan(e1, e2 interface{}) bool {
- return e1.(*entry).key < e2.(*entry).key
-}
-
-func entryKeyFn(it interface{}) int {
- return it.(*entry).key
-}
-
-func entryValueFn(it interface{}) interface{} {
- return it.(*entry).value
-}
-
-func entryEntryFn(key int) interface{} {
- return &entry{key: key}
-}
-
-type entry2 struct {
- key int
- value string
-}
-
-func entry2LessThan(e1, e2 interface{}) bool {
- return e1.(*entry2).key < e2.(*entry2).key
-}
-
-func entry2KeyFn(it interface{}) int {
- return it.(*entry2).key
-}
-
-func entry2ValueFn(it interface{}) interface{} {
- return it.(*entry2).value
-}
-
-func entry2EntryFn(key int) interface{} {
- return &entry2{key: key}
-}
-
-func checkMaps(t *testing.T, s functional.Set, elements map[int]interface{},
- keyFn func(interface{}) int,
- valueFn func(interface{}) interface{},
- entryFn func(int) interface{}) {
- checkInvariants(t, s)
-
- s.Iter(func(it interface{}) bool {
- key := keyFn(it)
- value := valueFn(it)
- if j, ok := elements[key]; !ok || j != value {
- t.Errorf("Expected %d, got %d", value, j)
- }
- return true
- })
- for i, x := range elements {
- if !s.Contains(entryFn(i)) {
- t.Errorf("Missing element: %d", i)
- }
- v, ok := s.Get(entryFn(i))
- if !ok {
- t.Errorf("Missing element: %d", i)
- } else {
- key := keyFn(v)
- value := valueFn(v)
- if key != i || value != x {
- t.Errorf("Expected (%d, %d), got (%v)", i, x, v)
- }
- }
- }
-
- if s.Len() != len(elements) {
- t.Errorf("Expected size %d, actual size %d", len(elements), s.Len())
- }
-}
-
-func TestSequentialMap(t *testing.T) {
- s := NewSet(entryLessThan)
- elements := make(map[int]interface{})
- for i := 0; i != kMaxElement; i++ {
- elements[i] = i + 1
- s = s.Put(&entry{key: i, value: i + 1})
- checkMaps(t, s, elements, entryKeyFn, entryValueFn, entryEntryFn)
- }
-
- for i := kMaxElement; i >= 0; i-- {
- delete(elements, i)
- s = s.Remove(&entry{key: i})
- checkMaps(t, s, elements, entryKeyFn, entryValueFn, entryEntryFn)
- }
-}
-
-func TestRandomMap(t *testing.T) {
- s := NewSet(entryLessThan)
- elements := make(map[int]interface{})
- for i := 0; i != kLoopCount; i++ {
- switch testutil.Rand.Intn(2) {
- case 0:
- // Insertion
- k := testutil.Rand.Intn(kMaxElement)
- v := testutil.Rand.Int()
- elements[k] = v
- s = s.Put(&entry{key: k, value: v})
- case 1:
- // Deletion
- k := testutil.Rand.Intn(kMaxElement)
- delete(elements, k)
- s = s.Remove(&entry{key: k})
- }
- checkMaps(t, s, elements, entryKeyFn, entryValueFn, entryEntryFn)
- }
-}
-
-func TestMapMap(t *testing.T) {
- s := NewSet(entryLessThan)
- elements := make(map[int]interface{})
- for i := 0; i != kMaxElement; i++ {
- elements[i] = i + 1
- s = s.Put(&entry{key: i, value: i + 1})
- checkMaps(t, s, elements, entryKeyFn, entryValueFn, entryEntryFn)
- }
-
- s2 := s.Map(func(it interface{}) interface{} {
- e := *it.(*entry)
- e.value++
- return &e
- }, entryLessThan)
- elements2 := make(map[int]interface{})
- for k, v := range elements {
- elements2[k] = v.(int) + 1
- }
- checkMaps(t, s2, elements2, entryKeyFn, entryValueFn, entryEntryFn)
-}
-
-func TestMapFold(t *testing.T) {
- s := NewSet(entryLessThan)
- elements := make(map[int]interface{})
- for i := 0; i != kMaxElement; i++ {
- elements[i] = i + 1
- s = s.Put(&entry{key: i, value: i + 1})
- checkMaps(t, s, elements, entryKeyFn, entryValueFn, entryEntryFn)
- }
-
- l := s.Fold(func(it, x interface{}) interface{} {
- l := x.([]entry)
- return append(l, *it.(*entry))
- }, ([]entry)(nil))
-
- s2 := NewSet(entryLessThan)
- for i, e := range l.([]entry) {
- e2 := e // Copy the entry.
- if e2.key != i || e2.value != i+1 {
- t.Errorf("Unexpected entry: %v", e2)
- }
- s2 = s2.Put(&e2)
- }
- checkMaps(t, s2, elements, entryKeyFn, entryValueFn, entryEntryFn)
-}
-
-func TestMapToNewType(t *testing.T) {
- s := NewSet(entryLessThan)
- elements := make(map[int]interface{})
- for i := 0; i != kMaxElement; i++ {
- elements[i] = i + 1
- s = s.Put(&entry{key: i, value: i + 1})
- }
- s2 := s.Map(func(it interface{}) interface{} {
- e := *it.(*entry)
- return &entry2{
- key: e.key,
- value: fmt.Sprintf("s%v", e.value),
- }
- }, entry2LessThan)
- elements2 := make(map[int]interface{})
- for k, v := range elements {
- elements2[k] = fmt.Sprintf("s%v", v)
- }
- checkMaps(t, s2, elements2, entry2KeyFn, entry2ValueFn, entry2EntryFn)
-}
-
-////////////////////////////////////////////////////////////////////////
-// Simple random benchmark
-
-// Number of elements to check
-var kMaxElementBench int = 1000
-
-// Table of random ops
-type opname int
-
-const (
- CONTAINS opname = iota
- PUT
- REMOVE
- ITERATE
-)
-
-type operation struct {
- op opname
- count int
-}
-
-var optable = [...]operation{
- operation{CONTAINS, 40},
- operation{PUT, 4},
- operation{REMOVE, 4},
- operation{ITERATE, 1}}
-
-func makeOperations() []opname {
- totalsize := 0
- for i := 0; i != len(optable); i++ {
- totalsize += optable[i].count
- }
-
- operations := make([]opname, totalsize)
- index := 0
- for i := 0; i != len(optable); i++ {
- op := optable[i]
- for j := 0; j != op.count; j++ {
- operations[index] = op.op
- index++
- }
- }
- return operations
-}
-
-func BenchmarkRandom(b *testing.B) {
- operations := makeOperations()
- s := NewSet(intCompare)
-
- for i := 0; i != b.N; i++ {
- switch operations[testutil.Rand.Intn(len(operations))] {
- case CONTAINS:
- s.Contains(testutil.Rand.Intn(kMaxElement))
-
- case PUT:
- s = s.Put(testutil.Rand.Intn(kMaxElement))
-
- case REMOVE:
- s = s.Remove(testutil.Rand.Intn(kMaxElement))
-
- case ITERATE:
- s.Iter(func(it interface{}) bool { return true })
- }
- }
-}
diff --git a/runtimes/google/lib/functional/set.go b/runtimes/google/lib/functional/set.go
deleted file mode 100644
index 1684c09..0000000
--- a/runtimes/google/lib/functional/set.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package functional
-
-// Set is a collection of elements that supports operations for adding and
-// removing elements, testing for membership, and enumerating the elements by
-// iteration.
-//
-// By "functional," we mean that all methods act like mathematical functions,
-// meaning that they return the same value on the same argument. That's one way
-// to put it, but the real consequence is that the set is immutable. Instead of
-// altering a set in-place, methods that perform alterations return a new set
-// with the changes. The argument set is unaffected.
-type Set interface {
- // Contains returns true iff the element "x" is in the set.
- Contains(x interface{}) bool
-
- // Get returns the actual entry associated with an element.
- Get(x interface{}) (interface{}, bool)
-
- // Put adds the element "x" to the set.
- Put(x interface{}) Set
-
- // Remove deletes the element "x" from the set.
- Remove(x interface{}) Set
-
- // IsEmpty returns true iff the set has no elements.
- IsEmpty() bool
-
- // Len returns the number of elements in the set.
- Len() int
-
- // Iterator returns an iterator referring to the first element in the set,
- // if there is one. If the set is empty, the iterator's IsValid method
- // returns false. Note that the set is immutable, so the values returned
- // from iteration are fixed at the time the Iterator is created and there
- // are no concurrency issues.
- Iterator() Iterator
-
- // Iter iterates through the set, applying the function f to each element.
- // Iteration terminates when all elements have been examined, or when the
- // function returns false. Iter is similar to Iterator, but it is included
- // to support standard functional idioms, similar to Map and Fold.
- Iter(f func(it interface{}) bool)
-
- // Map applies a function to each element of the set in order, replacing the
- // elements value. The ordering of the elements must be preserved under the
- // new comparator.
- //
- // { e1, e2, ..., eN }.Map(f, cmp) = { f(e1), f(e2), ..., f(eN) }
- //
- // Typically, this is use in dictionaries to update the value part of a
- // key/value pair, keeping the key unchanged, so preserving the order.
- Map(f func(it interface{}) interface{}, cmp Comparator) Set
-
- // Fold applies a function to the elements of the set in order, accumulating the
- // results.
- //
- // { e1, e2, ..., eN }.Fold(f, x) = f(eN, ... f(e2, f(e1, x)))
- //
- // For example, a sum could be computed as follows.
- //
- // s.Fold(func (it, accum interface{}) interface{} {
- // return it.(int) + accum.(int)
- // })
- Fold(f func(it, x interface{}) interface{}, x interface{}) interface{}
-}
-
-// Comparator is the type of ordering relations. The function returns true iff
-// the first argument is less than the second.
-//
-// The relation must be a strict weak order, which means that 1) it is a partial
-// order, and 2) equality is transitive (a == b iff !(a < b) && !(b < a)).
-type Comparator func(it1, it2 interface{}) bool
-
-// Iterator is the type of iterators over set elements.
-type Iterator interface {
- // IsValid returns true iff the iterator refers to a valid set element.
- IsValid() bool
-
- // Get returns the element that the iterator refers to. Undefined if
- // IsValid is false.
- Get() interface{}
-
- // Next advances the iterator to the next element in the set.
- Next()
-}
diff --git a/runtimes/google/rt/rt_test.go b/runtimes/google/rt/rt_test.go
index e391f1f..84085db 100644
--- a/runtimes/google/rt/rt_test.go
+++ b/runtimes/google/rt/rt_test.go
@@ -10,7 +10,6 @@
"testing"
"time"
- "veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/options"
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/security"
@@ -23,23 +22,6 @@
vsecurity "veyron.io/veyron/veyron/security"
)
-type context struct {
- local security.Principal
-}
-
-func (*context) Timestamp() (t time.Time) { return t }
-func (*context) Method() string { return "" }
-func (*context) MethodTags() []interface{} { return nil }
-func (*context) Name() string { return "" }
-func (*context) Suffix() string { return "" }
-func (*context) Label() (l security.Label) { return }
-func (*context) Discharges() map[string]security.Discharge { return nil }
-func (c *context) LocalPrincipal() security.Principal { return c.local }
-func (*context) LocalBlessings() security.Blessings { return nil }
-func (*context) RemoteBlessings() security.Blessings { return nil }
-func (*context) LocalEndpoint() naming.Endpoint { return nil }
-func (*context) RemoteEndpoint() naming.Endpoint { return nil }
-
func init() {
testutil.Init()
modules.RegisterChild("child", "", child)
@@ -120,7 +102,8 @@
return fmt.Errorf("rt.Principal().BlessingStore().Default() returned nil")
}
- if n := len(blessings.ForContext(&context{local: p})); n != 1 {
+ ctx := security.NewContext(&security.ContextParams{LocalPrincipal: p})
+ if n := len(blessings.ForContext(ctx)); n != 1 {
fmt.Errorf("rt.Principal().BlessingStore().Default() returned Blessing %v with %d recognized blessings, want exactly one recognized blessing", blessings, n)
}
return nil
diff --git a/runtimes/google/rt/security.go b/runtimes/google/rt/security.go
index 67814f1..86223d1 100644
--- a/runtimes/google/rt/security.go
+++ b/runtimes/google/rt/security.go
@@ -6,6 +6,7 @@
"os/user"
"strconv"
+ "veyron.io/veyron/veyron/lib/stats"
vsecurity "veyron.io/veyron/veyron/security"
"veyron.io/veyron/veyron/security/agent"
@@ -18,6 +19,16 @@
}
func (rt *vrt) initSecurity(credentials string) error {
+ if err := rt.setupPrincipal(credentials); err != nil {
+ return err
+ }
+ stats.NewString("security/principal/key").Set(rt.principal.PublicKey().String())
+ stats.NewStringFunc("security/principal/blessingstore", rt.principal.BlessingStore().DebugString)
+ stats.NewStringFunc("security/principal/blessingroots", rt.principal.Roots().DebugString)
+ return nil
+}
+
+func (rt *vrt) setupPrincipal(credentials string) error {
if rt.principal != nil {
return nil
}
diff --git a/security/acl_authorizer_test.go b/security/acl_authorizer_test.go
index f8785fa..98b0e2a 100644
--- a/security/acl_authorizer_test.go
+++ b/security/acl_authorizer_test.go
@@ -5,33 +5,10 @@
"os"
"runtime"
"testing"
- "time"
- "veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/security"
)
-// context implements security.Context.
-type context struct {
- localPrincipal security.Principal
- localBlessings, remoteBlessings security.Blessings
- method string
- label security.Label
-}
-
-func (c *context) Timestamp() (t time.Time) { return t }
-func (c *context) Method() string { return c.method }
-func (c *context) MethodTags() []interface{} { return nil }
-func (c *context) Name() string { return "" }
-func (c *context) Suffix() string { return "" }
-func (c *context) Label() security.Label { return c.label }
-func (c *context) Discharges() map[string]security.Discharge { return nil }
-func (c *context) LocalPrincipal() security.Principal { return c.localPrincipal }
-func (c *context) LocalBlessings() security.Blessings { return c.localBlessings }
-func (c *context) RemoteBlessings() security.Blessings { return c.remoteBlessings }
-func (c *context) LocalEndpoint() naming.Endpoint { return nil }
-func (c *context) RemoteEndpoint() naming.Endpoint { return nil }
-
func saveACLToTempFile(acl security.ACL) string {
f, err := ioutil.TempFile("", "saved_acl")
if err != nil {
@@ -62,31 +39,25 @@
pserver, server = newPrincipal("server")
_, imposter = newPrincipal("server")
palice, alice = newPrincipal("alice")
+ aliceServer = bless(palice, pserver, alice, "server")
- serverAlice = bless(pserver, palice, server, "alice")
- aliceServer = bless(palice, pserver, alice, "server")
-
- ctx = &context{
- localPrincipal: pserver,
- localBlessings: server,
- }
-
+ ctxp = &security.ContextParams{LocalPrincipal: pserver, LocalBlessings: server}
tests = []struct {
remote security.Blessings
isAuthorized bool
}{
{server, true},
{imposter, false},
- {serverAlice, false},
// A principal talking to itself (even if with a different blessing) is authorized.
// TODO(ashankar,ataly): Is this a desired property?
{aliceServer, true},
}
)
for _, test := range tests {
- ctx.remoteBlessings = test.remote
+ ctxp.RemoteBlessings = test.remote
+ ctx := security.NewContext(ctxp)
if got, want := authorizer.Authorize(ctx), test.isAuthorized; (got == nil) != want {
- t.Errorf("%s:%d: %+v.Authorize(&context{local: %v, remote: %v}) returned error: %v, want error: %v", file, line, authorizer, ctx.localBlessings, ctx.remoteBlessings, got, !want)
+ t.Errorf("%s:%d: %+v.Authorize(%v) returned error: %v, want error: %v", file, line, authorizer, ctx, got, !want)
}
}
}
@@ -119,13 +90,23 @@
// valid or invalid label.
for _, u := range users {
for _, l := range security.ValidLabels {
- ctx := &context{localPrincipal: pserver, localBlessings: server, remoteBlessings: u, label: l}
+ ctx := security.NewContext(&security.ContextParams{
+ LocalPrincipal: pserver,
+ LocalBlessings: server,
+ RemoteBlessings: u,
+ MethodTags: []interface{}{l},
+ })
if got := authorizer.Authorize(ctx); got == nil {
t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
}
}
- ctx := &context{localPrincipal: pserver, localBlessings: server, remoteBlessings: u, label: invalidLabel}
+ ctx := security.NewContext(&security.ContextParams{
+ LocalPrincipal: pserver,
+ LocalBlessings: server,
+ RemoteBlessings: u,
+ MethodTags: []interface{}{invalidLabel},
+ })
if got := authorizer.Authorize(ctx); got == nil {
t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
}
@@ -164,9 +145,14 @@
_, file, line, _ := runtime.Caller(1)
for user, labels := range expectations {
for _, l := range security.ValidLabels {
- ctx := &context{remoteBlessings: user, localBlessings: server, localPrincipal: pserver, label: l}
+ ctx := security.NewContext(&security.ContextParams{
+ LocalPrincipal: pserver,
+ LocalBlessings: server,
+ RemoteBlessings: user,
+ MethodTags: []interface{}{l},
+ })
if got, want := authorizer.Authorize(ctx), labels.HasLabel(l); (got == nil) != want {
- t.Errorf("%s:%d: %+v.Authorize(&context{remoteBlessings: %v, label: %v}) returned error: %v, want error: %v", file, line, authorizer, user, l, got, !want)
+ t.Errorf("%s:%d: %+v.Authorize(%v) returned error: %v, want error: %v", file, line, authorizer, ctx, got, !want)
}
}
}
diff --git a/security/blessingroots.go b/security/blessingroots.go
index 5f314d8..9883122 100644
--- a/security/blessingroots.go
+++ b/security/blessingroots.go
@@ -11,17 +11,12 @@
"veyron.io/veyron/veyron2/security"
)
-const (
- blessingRootsDataFile = "blessingroots.data"
- blessingRootsSigFile = "blessingroots.sig"
-)
-
// blessingRoots implements security.BlessingRoots.
type blessingRoots struct {
- dir string
- signer serialization.Signer
- mu sync.RWMutex
- store map[string][]security.BlessingPattern // GUARDED_BY(mu)
+ persistedData SerializerReaderWriter
+ signer serialization.Signer
+ mu sync.RWMutex
+ store map[string][]security.BlessingPattern // GUARDED_BY(mu)
}
func storeMapKey(root security.PublicKey) (string, error) {
@@ -93,10 +88,14 @@
}
func (br *blessingRoots) save() error {
- if (br.signer == nil) && (br.dir == "") {
+ if (br.signer == nil) && (br.persistedData == nil) {
return nil
}
- return encodeAndStore(br.store, br.dir, blessingRootsDataFile, blessingRootsSigFile, br.signer)
+ data, signature, err := br.persistedData.Writers()
+ if err != nil {
+ return err
+ }
+ return encodeAndStore(br.store, data, signature, br.signer)
}
// newInMemoryBlessingRoots returns an in-memory security.BlessingRoots.
@@ -108,27 +107,26 @@
}
}
-// newPersistingBlessingRoots returns a security.BlessingRoots that signs
-// and persists all updates to the provided directory. Signing is carried
-// out using the provided signer.
-//
-// The returned BlessingRoots is initialized from the existing data present
-// in the directory. The data is verified to have been written by a persisting
-// BlessingRoots object constructed from the same signer.
-//
-// Any errors obtained in reading or verifying the data are returned.
-func newPersistingBlessingRoots(directory string, signer serialization.Signer) (security.BlessingRoots, error) {
- if directory == "" || signer == nil {
- return nil, errors.New("directory or signer is not specified")
+// newPersistingBlessingRoots returns a security.BlessingRoots for a principal
+// that is initialized with the persisted data. The returned security.BlessingRoots
+// also persists any updates to its state.
+func newPersistingBlessingRoots(persistedData SerializerReaderWriter, signer serialization.Signer) (security.BlessingRoots, error) {
+ if persistedData == nil || signer == nil {
+ return nil, errors.New("persisted data or signer is not specified")
}
br := &blessingRoots{
- store: make(map[string][]security.BlessingPattern),
- dir: directory,
- signer: signer,
+ store: make(map[string][]security.BlessingPattern),
+ persistedData: persistedData,
+ signer: signer,
}
-
- if err := decodeFromStorage(&br.store, br.dir, blessingRootsDataFile, blessingRootsSigFile, br.signer.PublicKey()); err != nil {
+ data, signature, err := br.persistedData.Readers()
+ if err != nil {
return nil, err
}
+ if (data != nil) && (signature != nil) {
+ if err := decodeFromStorage(&br.store, data, signature, br.signer.PublicKey()); err != nil {
+ return nil, err
+ }
+ }
return br, nil
}
diff --git a/security/blessingstore.go b/security/blessingstore.go
index b135580..2a03456 100644
--- a/security/blessingstore.go
+++ b/security/blessingstore.go
@@ -13,11 +13,6 @@
"veyron.io/veyron/veyron2/vlog"
)
-const (
- blessingStoreDataFile = "blessingstore.data"
- blessingStoreSigFile = "blessingstore.sig"
-)
-
var errStoreAddMismatch = errors.New("blessing's public key does not match store's public key")
type persistentState struct {
@@ -33,45 +28,45 @@
// blessingStore implements security.BlessingStore.
type blessingStore struct {
- publicKey security.PublicKey
- dir string
- signer serialization.Signer
- mu sync.RWMutex
- state persistentState // GUARDED_BY(mu)
+ publicKey security.PublicKey
+ persistedData SerializerReaderWriter
+ signer serialization.Signer
+ mu sync.RWMutex
+ state persistentState // GUARDED_BY(mu)
}
-func (s *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+func (bs *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
if !forPeers.IsValid() {
return nil, fmt.Errorf("%q is an invalid BlessingPattern", forPeers)
}
- if blessings != nil && !reflect.DeepEqual(blessings.PublicKey(), s.publicKey) {
+ if blessings != nil && !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
return nil, errStoreAddMismatch
}
- s.mu.Lock()
- defer s.mu.Unlock()
- old, hadold := s.state.Store[forPeers]
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ old, hadold := bs.state.Store[forPeers]
if blessings != nil {
- s.state.Store[forPeers] = blessings
+ bs.state.Store[forPeers] = blessings
} else {
- delete(s.state.Store, forPeers)
+ delete(bs.state.Store, forPeers)
}
- if err := s.save(); err != nil {
+ if err := bs.save(); err != nil {
if hadold {
- s.state.Store[forPeers] = old
+ bs.state.Store[forPeers] = old
} else {
- delete(s.state.Store, forPeers)
+ delete(bs.state.Store, forPeers)
}
return nil, err
}
return old, nil
}
-func (s *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
- s.mu.RLock()
- defer s.mu.RUnlock()
+func (bs *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
+ bs.mu.RLock()
+ defer bs.mu.RUnlock()
var ret security.Blessings
- for pattern, blessings := range s.state.Store {
+ for pattern, blessings := range bs.state.Store {
if pattern.MatchedBy(peerBlessings...) {
if union, err := security.UnionOfBlessings(ret, blessings); err != nil {
vlog.Errorf("UnionOfBlessings(%v, %v) failed: %v, dropping the latter from BlessingStore.ForPeers(%v)", ret, blessings, err, peerBlessings)
@@ -83,35 +78,35 @@
return ret
}
-func (s *blessingStore) Default() security.Blessings {
- s.mu.RLock()
- defer s.mu.RUnlock()
- if s.state.Default != nil {
- return s.state.Default
+func (bs *blessingStore) Default() security.Blessings {
+ bs.mu.RLock()
+ defer bs.mu.RUnlock()
+ if bs.state.Default != nil {
+ return bs.state.Default
}
- return s.ForPeer()
+ return bs.ForPeer()
}
-func (s *blessingStore) SetDefault(blessings security.Blessings) error {
- s.mu.Lock()
- defer s.mu.Unlock()
- if !reflect.DeepEqual(blessings.PublicKey(), s.publicKey) {
+func (bs *blessingStore) SetDefault(blessings security.Blessings) error {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ if !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
return errStoreAddMismatch
}
- oldDefault := s.state.Default
- s.state.Default = blessings
- if err := s.save(); err != nil {
- s.state.Default = oldDefault
+ oldDefault := bs.state.Default
+ bs.state.Default = blessings
+ if err := bs.save(); err != nil {
+ bs.state.Default = oldDefault
}
return nil
}
-func (s *blessingStore) PublicKey() security.PublicKey {
- return s.publicKey
+func (bs *blessingStore) PublicKey() security.PublicKey {
+ return bs.publicKey
}
-func (s *blessingStore) String() string {
- return fmt.Sprintf("{state: %v, publicKey: %v, dir: %v}", s.state, s.publicKey, s.dir)
+func (bs *blessingStore) String() string {
+ return fmt.Sprintf("{state: %v, publicKey: %v}", bs.state, bs.publicKey)
}
// DebugString return a human-readable string encoding of the store
@@ -122,22 +117,26 @@
// <pattern> : <blessings>
// ...
// <pattern> : <blessings>
-func (br *blessingStore) DebugString() string {
+func (bs *blessingStore) DebugString() string {
const format = "%-30s : %s\n"
- b := bytes.NewBufferString(fmt.Sprintf("Default blessings: %v\n", br.state.Default))
+ b := bytes.NewBufferString(fmt.Sprintf("Default blessings: %v\n", bs.state.Default))
b.WriteString(fmt.Sprintf(format, "Peer pattern", "Blessings"))
- for pattern, blessings := range br.state.Store {
+ for pattern, blessings := range bs.state.Store {
b.WriteString(fmt.Sprintf(format, pattern, blessings))
}
return b.String()
}
-func (s *blessingStore) save() error {
- if (s.signer == nil) && (s.dir == "") {
+func (bs *blessingStore) save() error {
+ if (bs.signer == nil) && (bs.persistedData == nil) {
return nil
}
- return encodeAndStore(s.state, s.dir, blessingStoreDataFile, blessingStoreSigFile, s.signer)
+ data, signature, err := bs.persistedData.Writers()
+ if err != nil {
+ return err
+ }
+ return encodeAndStore(bs.state, data, signature, bs.signer)
}
// newInMemoryBlessingStore returns an in-memory security.BlessingStore for a
@@ -152,33 +151,31 @@
}
// newPersistingBlessingStore returns a security.BlessingStore for a principal
-// that persists all updates to the specified directory and uses the provided
-// signer to ensure integrity of data read from the filesystem.
-//
-// The returned BlessingStore is initialized from the existing data present in
-// the directory. The data is verified to have been written by a persisting
-// BlessingStore object constructed from the same signer.
-//
-// Any errors obtained in reading or verifying the data are returned.
-func newPersistingBlessingStore(directory string, signer serialization.Signer) (security.BlessingStore, error) {
- if directory == "" || signer == nil {
- return nil, errors.New("directory or signer is not specified")
+// that is initialized with the persisted data. The returned security.BlessingStore
+// also persists any updates to its state.
+func newPersistingBlessingStore(persistedData SerializerReaderWriter, signer serialization.Signer) (security.BlessingStore, error) {
+ if persistedData == nil || signer == nil {
+ return nil, errors.New("persisted data or signer is not specified")
}
- s := &blessingStore{
- publicKey: signer.PublicKey(),
- state: persistentState{Store: make(map[security.BlessingPattern]security.Blessings)},
- dir: directory,
- signer: signer,
+ bs := &blessingStore{
+ publicKey: signer.PublicKey(),
+ state: persistentState{Store: make(map[security.BlessingPattern]security.Blessings)},
+ persistedData: persistedData,
+ signer: signer,
}
-
- if err := decodeFromStorage(&s.state, s.dir, blessingStoreDataFile, blessingStoreSigFile, s.signer.PublicKey()); err != nil {
+ data, signature, err := bs.persistedData.Readers()
+ if err != nil {
return nil, err
}
-
- for _, b := range s.state.Store {
- if !reflect.DeepEqual(b.PublicKey(), s.publicKey) {
- return nil, fmt.Errorf("directory contains Blessings: %v that are not for the provided PublicKey: %v", b, s.publicKey)
+ if data != nil && signature != nil {
+ if err := decodeFromStorage(&bs.state, data, signature, bs.signer.PublicKey()); err != nil {
+ return nil, err
}
}
- return s, nil
+ for _, b := range bs.state.Store {
+ if !reflect.DeepEqual(b.PublicKey(), bs.publicKey) {
+ return nil, fmt.Errorf("directory contains Blessings: %v that are not for the provided PublicKey: %v", b, bs.publicKey)
+ }
+ }
+ return bs, nil
}
diff --git a/security/principal.go b/security/principal.go
index f0dba02..7247434 100644
--- a/security/principal.go
+++ b/security/principal.go
@@ -9,7 +9,15 @@
"veyron.io/veyron/veyron2/security"
)
-const privateKeyFile = "privatekey.pem"
+const (
+ blessingStoreDataFile = "blessingstore.data"
+ blessingStoreSigFile = "blessingstore.sig"
+
+ blessingRootsDataFile = "blessingroots.data"
+ blessingRootsSigFile = "blessingroots.sig"
+
+ privateKeyFile = "privatekey.pem"
+)
// NewPrincipal mints a new private key and generates a principal based on
// this key, storing its BlessingRoots and BlessingStore in memory.
@@ -27,7 +35,7 @@
return security.CreatePrincipal(signer, newInMemoryBlessingStore(signer.PublicKey()), newInMemoryBlessingRoots())
}
-// NewPersistentPrincipalForSigner creates a new principal using the provided Signer and a
+// NewPersistentPrincipalFromSigner creates a new principal using the provided Signer and a
// partial state (i.e., BlessingRoots, BlessingStore) that is read from the provided directory 'dir'.
// Changes to the partial state are persisted and commited to the same directory; the provided
// signer isn't persisted: the caller is expected to persist it separately or use the
@@ -100,11 +108,22 @@
if err != nil {
return nil, fmt.Errorf("failed to create serialization.Signer: %v", err)
}
- roots, err := newPersistingBlessingRoots(dir, serializationSigner)
+ dataFile := path.Join(dir, blessingRootsDataFile)
+ signatureFile := path.Join(dir, blessingRootsSigFile)
+ fs, err := NewFileSerializer(dataFile, signatureFile)
+ if err != nil {
+ return nil, err
+ }
+ roots, err := newPersistingBlessingRoots(fs, serializationSigner)
if err != nil {
return nil, fmt.Errorf("failed to load BlessingRoots from %q: %v", dir, err)
}
- store, err := newPersistingBlessingStore(dir, serializationSigner)
+ dataFile = path.Join(dir, blessingStoreDataFile)
+ signatureFile = path.Join(dir, blessingStoreSigFile)
+ if fs, err = NewFileSerializer(dataFile, signatureFile); err != nil {
+ return nil, err
+ }
+ store, err := newPersistingBlessingStore(fs, serializationSigner)
if err != nil {
return nil, fmt.Errorf("failed to load BlessingStore from %q: %v", dir, err)
}
diff --git a/security/serializer_reader_writer.go b/security/serializer_reader_writer.go
index 55248e4..633456d 100644
--- a/security/serializer_reader_writer.go
+++ b/security/serializer_reader_writer.go
@@ -2,6 +2,7 @@
import (
"io"
+ "os"
)
// SerializerReaderWriter is a factory for managing the readers and writers used for
@@ -14,3 +15,51 @@
// integrity signature.
Writers() (data io.WriteCloser, signature io.WriteCloser, err error)
}
+
+// FileSerializer implements SerializerReaderWriter that persists state to files.
+type FileSerializer struct {
+ data *os.File
+ signature *os.File
+
+ dataFilePath string
+ signatureFilePath string
+}
+
+// NewFileSerializer creates a FileSerializer with the given data and signature files.
+func NewFileSerializer(dataFilePath, signatureFilePath string) (*FileSerializer, error) {
+ data, err := os.Open(dataFilePath)
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ signature, err := os.Open(signatureFilePath)
+ if err != nil && !os.IsNotExist(err) {
+ return nil, err
+ }
+ return &FileSerializer{
+ data: data,
+ signature: signature,
+ dataFilePath: dataFilePath,
+ signatureFilePath: signatureFilePath,
+ }, nil
+}
+
+func (fs *FileSerializer) Readers() (io.ReadCloser, io.ReadCloser, error) {
+ if fs.data == nil || fs.signature == nil {
+ return nil, nil, nil
+ }
+ return fs.data, fs.signature, nil
+}
+
+func (fs *FileSerializer) Writers() (io.WriteCloser, io.WriteCloser, error) {
+ // Remove previous version of the files
+ os.Remove(fs.dataFilePath)
+ os.Remove(fs.signatureFilePath)
+ var err error
+ if fs.data, err = os.Create(fs.dataFilePath); err != nil {
+ return nil, nil, err
+ }
+ if fs.signature, err = os.Create(fs.signatureFilePath); err != nil {
+ return nil, nil, err
+ }
+ return fs.data, fs.signature, nil
+}
diff --git a/security/storage.go b/security/storage.go
index 0dd755b..2fdd8cd 100644
--- a/security/storage.go
+++ b/security/storage.go
@@ -1,32 +1,19 @@
package security
import (
- "io/ioutil"
- "os"
- "path"
+ "fmt"
+ "io"
"veyron.io/veyron/veyron/security/serialization"
-
"veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/vom"
)
-func encodeAndStore(obj interface{}, dir, dataFile, sigFile string, signer serialization.Signer) error {
- // Save the object to temporary data and signature files, and then move
- // those files to the actual data and signature file. This reduces the
- // risk of loosing all saved data on disk in the event of a Write failure.
- data, err := ioutil.TempFile(dir, "data")
- if err != nil {
- return err
+func encodeAndStore(obj interface{}, data, signature io.WriteCloser, signer serialization.Signer) error {
+ if data == nil || signature == nil {
+ return fmt.Errorf("invalid data/signature handles data:%v sig:%v", data, signature)
}
- defer os.Remove(data.Name())
- sig, err := ioutil.TempFile(dir, "sig")
- if err != nil {
- return err
- }
- defer os.Remove(sig.Name())
-
- swc, err := serialization.NewSigningWriteCloser(data, sig, signer, nil)
+ swc, err := serialization.NewSigningWriteCloser(data, signature, signer, nil)
if err != nil {
return err
}
@@ -34,32 +21,16 @@
swc.Close()
return err
}
- if err := swc.Close(); err != nil {
- return err
- }
-
- if err := os.Rename(data.Name(), path.Join(dir, dataFile)); err != nil {
- return err
- }
- return os.Rename(sig.Name(), path.Join(dir, sigFile))
+ return swc.Close()
}
-func decodeFromStorage(obj interface{}, dir, dataFile, sigFile string, publicKey security.PublicKey) error {
- data, dataErr := os.Open(path.Join(dir, dataFile))
- defer data.Close()
- sig, sigErr := os.Open(path.Join(dir, sigFile))
- defer sig.Close()
-
- switch {
- case os.IsNotExist(dataErr) && os.IsNotExist(sigErr):
- return nil
- case dataErr != nil:
- return dataErr
- case sigErr != nil:
- return sigErr
+func decodeFromStorage(obj interface{}, data, signature io.ReadCloser, publicKey security.PublicKey) error {
+ if data == nil || signature == nil {
+ return fmt.Errorf("invalid data/signature handles data:%v sig:%v", data, signature)
}
-
- vr, err := serialization.NewVerifyingReader(data, sig, publicKey)
+ defer data.Close()
+ defer signature.Close()
+ vr, err := serialization.NewVerifyingReader(data, signature, publicKey)
if err != nil {
return err
}
diff --git a/services/GO.PACKAGE b/services/GO.PACKAGE
index c423766..7a09896 100644
--- a/services/GO.PACKAGE
+++ b/services/GO.PACKAGE
@@ -2,9 +2,11 @@
"dependencies": {
"incoming": [
{"allow": "veyron.io/veyron/veyron/services/..."},
+ {"allow": "veyron.io/veyron/veyron/lib/...", "comment":"temporarily allowing dependency from lib"},
+ {"allow": "veyron.io/veyron/veyron/profiles/...", "comment":"temporarily allowing dependency from profiles"},
{"allow": "veyron.io/veyron/veyron/tools/...", "comment":"temporarily allowing dependency from veyron/tools"},
{"allow": "veyron.io/veyron/veyron/runtimes/google/...", "comment":"temporarily allowing dependency from veyron/runtimes/google"},
- {"allow": "veyron.io/veyron/veyron/lib/...", "comment":"temporarily allowing dependency from lib"},
+ {"allow": "veyron.io/jni/veyron/runtimes/google/...", "comment":"temporarily allowing dependency from veyron/runtimes/google"},
{"deny": "..."}
]
}
diff --git a/services/mgmt/binary/binaryd/test.sh b/services/mgmt/binary/binaryd/test.sh
index 2fd1661..51b7201 100755
--- a/services/mgmt/binary/binaryd/test.sh
+++ b/services/mgmt/binary/binaryd/test.sh
@@ -23,7 +23,7 @@
# Start the binary repository daemon.
local -r REPO="binaryd-test-repo"
- shell_test::start_server "${BINARYD_BIN}" --name="${REPO}" --veyron.tcp.address=127.0.0.1:0 \
+ shell_test::start_server "${BINARYD_BIN}" --name="${REPO}" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
|| shell_test::fail "line ${LINENO} failed to start binaryd"
local -r HTTP_ADDR=$(grep 'HTTP server at: "' "${START_SERVER_LOG_FILE}" | sed -e 's/^.*HTTP server at: "//' | sed -e 's/"$//')
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 47104a5..f048999 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -9,6 +9,12 @@
// <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
+// acls/
+// data - the ACL data for this
+// installation. Controls acces to
+// Start, Uinstall, Update, UpdateTo
+// and Revert.
+// signature - the signature for the ACLs in data
// <status> - one of the values for installationState enum
// origin - object name for application envelope
// <version 1 timestamp>/ - timestamp of when the version was downloaded
@@ -25,6 +31,12 @@
// logs/ - stderr/stdout and log files generated by instance
// info - metadata for the instance (such as app cycle manager name and process id)
// version - symbolic link to installation version for the instance
+// acls/
+// data - the ACLs for this instance. These
+// ACLs control access to Refresh,
+// Restart, Resume, Stop and
+// Suspend.
+// signature - the signature for these ACLs.
// <status> - one of the values for instanceState enum
// systemname - the system name used to execute this instance
// instance-<id b>
@@ -97,6 +109,7 @@
"os"
"os/exec"
"os/user"
+ "path"
"path/filepath"
"reflect"
"strings"
@@ -165,6 +178,9 @@
// instance.
suffix []string
uat BlessingSystemAssociationStore
+ locks aclLocks
+ // Reference to the nodemanager top-level ACL list.
+ nodeACL security.ACL
}
func saveEnvelope(dir string, envelope *application.Envelope) error {
@@ -304,7 +320,30 @@
return versionDir, updateLink(versionDir, filepath.Join(installationDir, "current"))
}
-func (i *appInvoker) Install(_ ipc.ServerContext, applicationVON string) (string, error) {
+// TODO(rjkroege): Refactor this code with the intance creation code.
+func initializeInstallationACLs(dir string, blessings []string, acl security.ACL) error {
+ // Start out with the claimant's ACLs and add the invoker's blessings.
+
+ var labels security.LabelSet
+ if acl.In == nil {
+ // The acl.In will be empty for an unclaimed node manager. In this case,
+ // create it.
+ acl.In = make(map[security.BlessingPattern]security.LabelSet)
+ }
+ labels = security.AllLabels
+
+ for _, name := range blessings {
+ // TODO(rjkroege): Use custom labels.
+ acl.In[security.BlessingPattern(name)] = labels
+ }
+
+ aclDir := path.Join(dir, "acls")
+ aclData := path.Join(aclDir, "data")
+ aclSig := path.Join(aclDir, "signature")
+ return writeACLs(aclData, aclSig, aclDir, acl)
+}
+
+func (i *appInvoker) Install(call ipc.ServerContext, applicationVON string) (string, error) {
if len(i.suffix) > 0 {
return "", errInvalidSuffix
}
@@ -333,6 +372,10 @@
if err := initializeInstallation(installationDir, active); err != nil {
return "", err
}
+
+ if err := initializeInstallationACLs(installationDir, call.RemoteBlessings().ForContext(call), i.nodeACL); err != nil {
+ return "", err
+ }
deferrer = nil
return naming.Join(envelope.Title, installationID), nil
}
@@ -357,17 +400,12 @@
return file, nil
}
-// installationDir returns the path to the directory containing the app
-// installation referred to by the invoker's suffix. Returns an error if the
-// suffix does not name an installation or if the named installation does not
-// exist.
-func (i *appInvoker) installationDir() (string, error) {
- components := i.suffix
+func installationDirCore(components []string, root string) (string, error) {
if nComponents := len(components); nComponents != 2 {
return "", errInvalidSuffix
}
app, installation := components[0], components[1]
- installationDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation))
+ installationDir := filepath.Join(root, applicationDirName(app), installationDirName(installation))
if _, err := os.Stat(installationDir); err != nil {
if os.IsNotExist(err) {
return "", errNotExist
@@ -469,6 +507,34 @@
return nil
}
+// installationDir returns the path to the directory containing the app
+// installation referred to by the invoker's suffix. Returns an error if the
+// suffix does not name an installation or if the named installation does not
+// exist.
+func (i *appInvoker) installationDir() (string, error) {
+ return installationDirCore(i.suffix, i.config.Root)
+}
+
+func initializeInstanceACLs(key, installationDir, instanceDir string, blessings []string, acl security.ACL) error {
+ if acl.In == nil {
+ // The acl.In will be empty for an unclaimed node manager. In this case,
+ // create it
+ acl.In = make(map[security.BlessingPattern]security.LabelSet)
+ }
+
+ labels := security.AllLabels
+ for _, name := range blessings {
+ // TODO(rjkroege): Use custom labels.
+ acl.In[security.BlessingPattern(name)] = labels
+ }
+
+ aclDir := path.Join(instanceDir, "acls")
+ aclData := path.Join(aclDir, "data")
+ aclSig := path.Join(aclDir, "signature")
+
+ return writeACLs(aclData, aclSig, aclDir, acl)
+}
+
// newInstance sets up the directory for a new application instance.
func (i *appInvoker) newInstance(call ipc.ServerContext) (string, string, error) {
installationDir, err := i.installationDir()
@@ -504,6 +570,10 @@
if err := initializeInstance(instanceDir, suspended); err != nil {
return instanceDir, instanceID, err
}
+
+ if err := initializeInstanceACLs(installationDir, installationDir, instanceDir, call.RemoteBlessings().ForContext(call), i.nodeACL); err != nil {
+ return instanceDir, instanceID, err
+ }
return instanceDir, instanceID, nil
}
@@ -676,6 +746,7 @@
func (i *appInvoker) Start(call ipc.ServerContext) ([]string, error) {
helper := i.config.Helper
instanceDir, instanceID, err := i.newInstance(call)
+
if err != nil {
cleanupDir(instanceDir, helper)
return nil, err
@@ -1071,3 +1142,41 @@
return
}
}
+
+// TODO(rjkroege): Refactor to eliminate redundancy with newAppSpecificAuthorizer.
+func dirFromSuffix(suffix []string, root string) (string, error) {
+ if len(suffix) == 2 {
+ p, err := installationDirCore(suffix, root)
+ if err != nil {
+ vlog.Errorf("dirFromSuffix failed: %v", err)
+ return "", err
+ }
+ return p, nil
+ } else if len(suffix) > 2 {
+ p, err := instanceDir(root, suffix[0:3])
+ if err != nil {
+ vlog.Errorf("dirFromSuffix failed: %v", err)
+ return "", err
+ }
+ return p, nil
+ }
+ return "", errInvalidSuffix
+}
+
+// TODO(rjkroege): Consider maintaining an in-memory ACL cache.
+// TODO(rjkroege): Excise the idea of the key. Use the dir instead.
+func (i *appInvoker) SetACL(_ ipc.ServerContext, acl security.ACL, etag string) error {
+ dir, err := dirFromSuffix(i.suffix, i.config.Root)
+ if err != nil {
+ return err
+ }
+ return setAppACL(i.locks, dir, dir, acl, etag)
+}
+
+func (i *appInvoker) GetACL(_ ipc.ServerContext) (acl security.ACL, etag string, err error) {
+ dir, err := dirFromSuffix(i.suffix, i.config.Root)
+ if err != nil {
+ return security.ACL{}, "", err
+ }
+ return getAppACL(i.locks, dir, dir)
+}
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index f41df15..b29347f 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -7,6 +7,7 @@
"fmt"
"io/ioutil"
"os"
+ "path"
"path/filepath"
"strings"
"sync"
@@ -37,6 +38,9 @@
updating *updatingState
}
+// aclLocks provides a mutex lock for each acl file path.
+type aclLocks map[string]*sync.Mutex
+
// dispatcher holds the state of the node manager dispatcher.
type dispatcher struct {
// acl/auth hold the acl and authorizer used to authorize access to the
@@ -54,6 +58,8 @@
// dispatcher methods.
mu sync.RWMutex
uat BlessingSystemAssociationStore
+ // TODO(rjkroege): Eliminate need for locks.
+ locks aclLocks
}
var _ ipc.Dispatcher = (*dispatcher)(nil)
@@ -93,6 +99,7 @@
},
config: config,
uat: uat,
+ locks: make(aclLocks),
}
// If there exists a signed ACL from a previous instance we prefer that.
aclFile, sigFile, _ := d.getACLFilePaths()
@@ -138,6 +145,7 @@
func (d *dispatcher) claimNodeManager(names []string, proof security.Blessings) error {
// TODO(gauthamt): Should we start trusting these identity providers?
+ // TODO(rjkroege): Scrub the state tree of installation and instance ACL files.
if len(names) == 0 {
vlog.Errorf("No names for claimer(%v) are trusted", proof)
return errOperationFailed
@@ -154,62 +162,158 @@
vlog.Errorf("Failed to getACL:%v", err)
return errOperationFailed
}
- return d.setACL(acl, etag, true /* store ACL on disk */)
+ if err := d.setACL(acl, etag, true /* store ACL on disk */); err != nil {
+ vlog.Errorf("Failed to setACL:%v", err)
+ return errOperationFailed
+ }
+ return nil
+}
+
+// TODO(rjkroege): Further refactor ACL-setting code.
+func setAppACL(locks aclLocks, key, dir string, acl security.ACL, etag string) error {
+ aclpath := path.Join(dir, "acls", "data")
+ sigpath := path.Join(dir, "acls", "signature")
+
+ // Acquire lock. Locks are per path to an acls file.
+ lck, contains := locks[key]
+ if !contains {
+ lck = new(sync.Mutex)
+ locks[key] = lck
+ }
+ lck.Lock()
+ defer lck.Unlock()
+
+ f, err := os.Open(aclpath)
+ if err != nil {
+ vlog.Errorf("LoadACL(%s) failed: %v", aclpath, err)
+ return err
+ }
+ defer f.Close()
+
+ curACL, err := vsecurity.LoadACL(f)
+ if err != nil {
+ vlog.Errorf("LoadACL(%s) failed: %v", aclpath, err)
+ return err
+ }
+ curEtag, err := computeEtag(curACL)
+ if err != nil {
+ vlog.Errorf("computeEtag failed: %v", err)
+ return err
+ }
+
+ if len(etag) > 0 && etag != curEtag {
+ return verror.Make(access.ErrBadEtag, fmt.Sprintf("etag mismatch in:%s vers:%s", etag, curEtag))
+ }
+
+ return writeACLs(aclpath, sigpath, dir, acl)
+}
+
+// TODO(rjkroege): Use the dir as the key.
+func getAppACL(locks aclLocks, key, dir string) (security.ACL, string, error) {
+ aclpath := path.Join(dir, "acls", "data")
+
+ // Acquire lock. Locks are per path to an acls file.
+ lck, contains := locks[key]
+ if !contains {
+ lck = new(sync.Mutex)
+ locks[key] = lck
+ }
+ lck.Lock()
+ defer lck.Unlock()
+
+ f, err := os.Open(aclpath)
+ if err != nil {
+ vlog.Errorf("LoadACL(%s) failed: %v", aclpath, err)
+ return security.ACL{}, "", err
+ }
+ defer f.Close()
+
+ acl, err := vsecurity.LoadACL(f)
+ if err != nil {
+ vlog.Errorf("LoadACL(%s) failed: %v", aclpath, err)
+ return security.ACL{}, "", err
+ }
+ curEtag, err := computeEtag(acl)
+ if err != nil {
+ return security.ACL{}, "", err
+ }
+
+ if err != nil {
+ return security.ACL{}, "", err
+ }
+ return acl, curEtag, nil
+}
+
+func computeEtag(acl security.ACL) (string, error) {
+ b := new(bytes.Buffer)
+ if err := vsecurity.SaveACL(b, acl); err != nil {
+ vlog.Errorf("Failed to save ACL:%v", err)
+ return "", err
+ }
+ // Update the acl/etag/authorizer for this dispatcher
+ md5hash := md5.Sum(b.Bytes())
+ etag := hex.EncodeToString(md5hash[:])
+ return etag, nil
+}
+
+func writeACLs(aclFile, sigFile, dir string, acl security.ACL) error {
+ // Create dir directory if it does not exist
+ os.MkdirAll(dir, os.FileMode(0700))
+ // Save the object to temporary data and signature files, and then move
+ // those files to the actual data and signature file.
+ data, err := ioutil.TempFile(dir, "data")
+ if err != nil {
+ vlog.Errorf("Failed to open tmpfile data:%v", err)
+ return errOperationFailed
+ }
+ defer os.Remove(data.Name())
+ sig, err := ioutil.TempFile(dir, "sig")
+ if err != nil {
+ vlog.Errorf("Failed to open tmpfile sig:%v", err)
+ return errOperationFailed
+ }
+ defer os.Remove(sig.Name())
+ writer, err := serialization.NewSigningWriteCloser(data, sig, rt.R().Principal(), nil)
+ if err != nil {
+ vlog.Errorf("Failed to create NewSigningWriteCloser:%v", err)
+ return errOperationFailed
+ }
+ if err = vsecurity.SaveACL(writer, acl); err != nil {
+ vlog.Errorf("Failed to SaveACL:%v", err)
+ return errOperationFailed
+ }
+ if err = writer.Close(); err != nil {
+ vlog.Errorf("Failed to Close() SigningWriteCloser:%v", err)
+ return errOperationFailed
+ }
+ if err := os.Rename(data.Name(), aclFile); err != nil {
+ return err
+ }
+ if err := os.Rename(sig.Name(), sigFile); err != nil {
+ return err
+ }
+ return nil
}
func (d *dispatcher) setACL(acl security.ACL, etag string, writeToFile bool) error {
d.mu.Lock()
defer d.mu.Unlock()
+ aclFile, sigFile, nodedata := d.getACLFilePaths()
+
if len(etag) > 0 && etag != d.etag {
return verror.Make(access.ErrBadEtag, fmt.Sprintf("etag mismatch in:%s vers:%s", etag, d.etag))
}
if writeToFile {
- // Create nodedata directory if it does not exist
- aclFile, sigFile, nodedata := d.getACLFilePaths()
- os.MkdirAll(nodedata, os.FileMode(0700))
- // Save the object to temporary data and signature files, and then move
- // those files to the actual data and signature file.
- data, err := ioutil.TempFile(nodedata, "data")
- if err != nil {
- vlog.Errorf("Failed to open tmpfile data:%v", err)
- return errOperationFailed
- }
- defer os.Remove(data.Name())
- sig, err := ioutil.TempFile(nodedata, "sig")
- if err != nil {
- vlog.Errorf("Failed to open tmpfile sig:%v", err)
- return errOperationFailed
- }
- defer os.Remove(sig.Name())
- writer, err := serialization.NewSigningWriteCloser(data, sig, rt.R().Principal(), nil)
- if err != nil {
- vlog.Errorf("Failed to create NewSigningWriteCloser:%v", err)
- return errOperationFailed
- }
- if err = vsecurity.SaveACL(writer, acl); err != nil {
- vlog.Errorf("Failed to SaveACL:%v", err)
- return errOperationFailed
- }
- if err = writer.Close(); err != nil {
- vlog.Errorf("Failed to Close() SigningWriteCloser:%v", err)
- return errOperationFailed
- }
- if err := os.Rename(data.Name(), aclFile); err != nil {
- return err
- }
- if err := os.Rename(sig.Name(), sigFile); err != nil {
+ if err := writeACLs(aclFile, sigFile, nodedata, acl); err != nil {
return err
}
}
- // update the etag for the ACL
- var b bytes.Buffer
- if err := vsecurity.SaveACL(&b, acl); err != nil {
- vlog.Errorf("Failed to save ACL:%v", err)
- return errOperationFailed
+
+ etag, err := computeEtag(acl)
+ if err != nil {
+ return err
}
- // Update the acl/etag/authorizer for this dispatcher
- md5hash := md5.Sum(b.Bytes())
- d.acl, d.etag, d.auth = acl, hex.EncodeToString(md5hash[:]), vsecurity.NewACLAuthorizer(acl)
+ d.acl, d.etag, d.auth = acl, etag, vsecurity.NewACLAuthorizer(acl)
return nil
}
@@ -220,7 +324,6 @@
}
// DISPATCHER INTERFACE IMPLEMENTATION
-
func (d *dispatcher) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
components := strings.Split(suffix, "/")
for i := 0; i < len(components); i++ {
@@ -287,16 +390,23 @@
return &proxyInvoker{remote, label, sigStub}, d.auth, nil
}
}
+ nodeACLs, _, err := d.getACL()
+ if err != nil {
+ return nil, nil, err
+ }
receiver := node.ApplicationServer(&appInvoker{
callback: d.internal.callback,
config: d.config,
suffix: components[1:],
uat: d.uat,
+ locks: d.locks,
+ nodeACL: nodeACLs,
})
- // TODO(caprita,rjkroege): Once we implement per-object ACLs
- // (i.e. each installation and instance), replace d.auth with
- // per-object authorizer.
- return ipc.ReflectInvoker(receiver), d.auth, nil
+ appSpecificAuthorizer, err := newAppSpecificAuthorizer(d.auth, d.config, components[1:])
+ if err != nil {
+ return nil, nil, err
+ }
+ return ipc.ReflectInvoker(receiver), appSpecificAuthorizer, nil
case configSuffix:
if len(components) != 2 {
return nil, nil, errInvalidSuffix
@@ -317,3 +427,32 @@
return nil, nil, errInvalidSuffix
}
}
+
+func newAppSpecificAuthorizer(sec security.Authorizer, config *config.State, suffix []string) (security.Authorizer, error) {
+ // TODO(rjkroege): This does not support <appname>.Start() to start all instances. Correct this.
+
+ // If we are attempting a method invocation against "apps/", we use the node-manager wide ACL.
+ if len(suffix) == 0 || len(suffix) == 1 {
+ return sec, nil
+ }
+ // Otherwise, we require a per-installation and per-instance ACL file.
+
+ if len(suffix) == 2 {
+ p, err := installationDirCore(suffix, config.Root)
+ if err != nil {
+ vlog.Errorf("newAppSpecificAuthorizer failed: %v", err)
+ return nil, err
+ }
+ p = path.Join(p, "acls", "data")
+ return vsecurity.NewFileACLAuthorizer(p), nil
+ } else if len(suffix) > 2 {
+ p, err := instanceDir(config.Root, suffix[0:3])
+ if err != nil {
+ vlog.Errorf("newAppSpecificAuthorizer failed: %v", err)
+ return nil, err
+ }
+ p = path.Join(p, "acls", "data")
+ return vsecurity.NewFileACLAuthorizer(p), nil
+ }
+ return nil, errInvalidSuffix
+}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 8367a64..9185c2f 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1,5 +1,6 @@
// TODO(caprita): This file is becoming unmanageable; split into several test
// files.
+// TODO(rjkroege): Add a more extensive unit test case to exercise ACL logic.
package impl_test
@@ -1285,7 +1286,7 @@
vlog.VI(2).Infof("other attempting to run an app without access. Should fail.")
startAppExpectError(t, appID, verror.NoAccess, otherRT)
- // Self will now let other also run apps.
+ // Self will now let other also install apps.
if err := nodeStub.AssociateAccount(selfRT.NewContext(), []string{"root/other"}, testUserName); err != nil {
t.Fatalf("AssociateAccount failed %v", err)
}
@@ -1294,11 +1295,28 @@
if err != nil {
t.Fatalf("GetACL failed %v", err)
}
- newACL.In["root/other/..."] = security.AllLabels
+ newACL.In["root/other/..."] = security.LabelSet(security.WriteLabel)
if err := nodeStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
t.Fatalf("SetACL failed %v", err)
}
+ // With the introduction of per installation and per instance ACLs, while other now
+ // has administrator permissions on the node manager, other doesn't have execution
+ // permissions for the app. So this will fail.
+ vlog.VI(2).Infof("other attempting to run an app still without access. Should fail.")
+ startAppExpectError(t, appID, verror.NoAccess, otherRT)
+
+ // But self can give other permissions to start applications.
+ vlog.VI(2).Infof("self attempting to give other permission to start %s", appID)
+ newACL, _, err = appStub(appID).GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL on appID: %v failed %v", appID, err)
+ }
+ newACL.In["root/other/..."] = security.LabelSet(security.ReadLabel)
+ if err = appStub(appID).SetACL(selfRT.NewContext(), newACL, ""); err != nil {
+ t.Fatalf("SetACL on appID: %v failed: %v", appID, err)
+ }
+
vlog.VI(2).Infof("other attempting to run an app with access. Should succeed.")
instance2ID := startApp(t, appID, otherRT)
verifyHelperArgs(t, pingCh, testUserName) // Wait until the app pings us that it's ready.
@@ -1309,6 +1327,16 @@
verifyHelperArgs(t, pingCh, testUserName) // Wait until the app pings us that it's ready.
suspendApp(t, appID, instance2ID, otherRT)
+ vlog.VI(2).Infof("Verify that other can install and run applications.")
+ otherAppID := installApp(t, otherRT)
+
+ vlog.VI(2).Infof("other attempting to run an app that other installed. Should succeed.")
+ instance4ID := startApp(t, otherAppID, otherRT)
+ verifyHelperArgs(t, pingCh, testUserName) // Wait until the app pings us that it's ready.
+
+ // Clean up.
+ stopApp(t, otherAppID, instance4ID, otherRT)
+
// Change the associated system name.
if err := nodeStub.AssociateAccount(selfRT.NewContext(), []string{"root/other"}, anotherTestUserName); err != nil {
t.Fatalf("AssociateAccount failed %v", err)
diff --git a/tools/debug/test.sh b/tools/debug/test.sh
index b3078b0..8c38207 100755
--- a/tools/debug/test.sh
+++ b/tools/debug/test.sh
@@ -62,7 +62,7 @@
shell_test::assert_eq "${GOT}" "${WANT}" "${LINENO}"
# Test stats read.
- GOT=$("${DEBUG_BIN}" stats read "${EP}/__debug/stats/ipc/server/*/ReadLog/latency-ms" 2> "${DBGLOG}" | wc -l) \
+ GOT=$("${DEBUG_BIN}" stats read "${EP}/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms" 2> "${DBGLOG}" | wc -l) \
|| (dumplogs "${DBGLOG}"; shell_test::fail "line ${LINENO}: failed to run debug")
shell_test::assert_gt "${GOT}" "0" "${LINENO}"
@@ -70,7 +70,7 @@
local TMP=$(shell::tmp_file)
touch "${TMP}"
local -r DEBUG_PID=$(shell::run_server "${shell_test_DEFAULT_SERVER_TIMEOUT}" "${TMP}" "${DBGLOG}" \
- "${DEBUG_BIN}" stats watch -raw "${EP}/__debug/stats/ipc/server/*/ReadLog/latency-ms")
+ "${DEBUG_BIN}" stats watch -raw "${EP}/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms")
shell::timed_wait_for "${shell_test_DEFAULT_MESSAGE_TIMEOUT}" "${TMP}" "ReadLog/latency-ms"
kill "${DEBUG_PID}"
grep -q "Count:1 " "${TMP}" || (dumplogs "${TMP}"; shell_test::fail "line ${LINENO}: failed to find expected output")
diff --git a/tools/mgmt/node/impl.go b/tools/mgmt/node/impl.go
index b538977..9758d72 100644
--- a/tools/mgmt/node/impl.go
+++ b/tools/mgmt/node/impl.go
@@ -45,6 +45,7 @@
ArgsLong: `
<application installation> is the veyron object name of the
application installation from which to start an instance.
+
<grant extension> is used to extend the default blessing of the
current principal when blessing the app instance.`,
}
@@ -74,6 +75,31 @@
return nil
}
+var cmdClaim = &cmdline.Command{
+ Run: runClaim,
+ Name: "claim",
+ Short: "Claim the node.",
+ Long: "Claim the node.",
+ ArgsName: "<node> <grant extension>",
+ ArgsLong: `
+<node> is the veyron object name of the node manager's app service.
+
+<grant extension> is used to extend the default blessing of the
+current principal when blessing the app instance.`,
+}
+
+func runClaim(cmd *cmdline.Command, args []string) error {
+ if expected, got := 2, len(args); expected != got {
+ return cmd.UsageErrorf("claim: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ nodeName, grant := args[0], args[1]
+ if err := node.NodeClient(nodeName).Claim(rt.R().NewContext(), &granter{p: rt.R().Principal(), extension: grant}); err != nil {
+ return fmt.Errorf("Claim failed: %v", err)
+ }
+ fmt.Fprintln(cmd.Stdout(), "Successfully claimed.")
+ return nil
+}
+
func root() *cmdline.Command {
return &cmdline.Command{
Name: "node",
@@ -81,6 +107,6 @@
Long: `
The node tool facilitates interaction with the veyron node manager.
`,
- Children: []*cmdline.Command{cmdInstall, cmdStart},
+ Children: []*cmdline.Command{cmdInstall, cmdStart, cmdClaim},
}
}