Merge "veyron/services/store: Implement Object.WatchGlob()."
diff --git a/examples/pipetobrowser/browser/index.html b/examples/pipetobrowser/browser/index.html
index bea82d7..9315525 100644
--- a/examples/pipetobrowser/browser/index.html
+++ b/examples/pipetobrowser/browser/index.html
@@ -23,6 +23,7 @@
<!-- TODO(aghassemi) Dynamic loading of plugins. Plugins should be able to use a provided module to load web components if they need to. We don't want to load all plugins for no reason! There could be hundreds of them -->
<link rel="import" href="pipe-viewers/builtin/console/component.html">
<link rel="import" href="pipe-viewers/builtin/git/status/component.html">
+ <link rel="import" href="pipe-viewers/builtin/vlog/component.html">
</head>
<body>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
index 610a0b1..b9d6e55 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -18,8 +18,6 @@
}
play(stream) {
- var listView = document.createElement('ul');
-
// TODO(aghassemi) let's have the plugin specify if they expect data in
// in binary or text so p2b can set the proper encoding for them rather
// than each plugin doing it like this.
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
new file mode 100644
index 0000000..e99e5f8
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
@@ -0,0 +1,24 @@
+tr.error {
+ background-color: #FF9700;
+}
+
+tr.fatal {
+ background-color: #DA4336;
+}
+
+tr.warning {
+ background-color: #FFEA3A;
+}
+
+tr.info {
+ background-color: #FAFAFA;
+}
+
+.lineNumber::before {
+ content: '#';
+}
+
+.lineNumber {
+ color: rgb(51, 103, 214);
+ margin-left: 0.1em;
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
new file mode 100644
index 0000000..d1a0fb7
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -0,0 +1,39 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+
+<polymer-element name="p2b-plugin-vlog">
+ <template>
+ <link rel="stylesheet" href="component.css">
+ <table summary="Data Grid displaying veyron log items in a tabular format.">
+ <thead>
+ <tr>
+ <th>Log Level</th>
+ <th>File</th>
+ <th>Message</th>
+ <th>Date</th>
+ <th>ThreadId</th>
+ </tr>
+ </thead>
+ <tbody>
+ <template repeat="{{ item in logItems }}">
+ <tr class="{{ item.level }}">
+ <td>{{ item.level }}</td>
+ <th scope="row">{{ item.file }}<span class="lineNumber">{{ item.fileLine }}<span/></th>
+ <td>{{ item.message }}</td>
+ <td>{{ item.date }}</td>
+ <td>{{ item.threadId }}</td>
+ </tr>
+ </template>
+ </tbody>
+ </table>
+
+ </template>
+ <script>
+ Polymer('p2b-plugin-vlog', {
+ /*
+ * List of log items to display
+ * @type {object}
+ */
+ logItems: []
+ });
+ </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
new file mode 100644
index 0000000..340b8af
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
@@ -0,0 +1,90 @@
+/*
+ * Parse utilities for veyron logs
+ * @fileoverview
+ */
+
+/*
+ * Parses a single line of text produced by veyron logger
+ * into an structured object representing it.
+ * Log lines have this form:
+ * Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+ * where the fields are defined as follows:
+ * L A single character, representing the log level (eg 'I' for INFO)
+ * mm The month (zero padded; ie May is '05')
+ * dd The day (zero padded)
+ * hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds
+ * threadid The space-padded thread ID as returned by GetTID()
+ * file The file name
+ * line The line number
+ * msg The user-supplied message
+ * @param {string} vlogLine A single line of
+ * by git status --short command
+ * @return {parser.item} A parsed object containing log level, date, file,
+ * line number, thread id and message.
+ */
+export function parse(vlogLine) {
+
+ var validLogLineRegEx = /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
+ var logParts = vlogLine.match(validLogLineRegEx);
+ if(!logParts || logParts.length != 8+1) { // 8 parts + 1 whole match
+ throw new Error('Invalid vlog line format. ' + vlogLine +
+ ' Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg.. pattern');
+ }
+
+ var L = logParts[1];
+ var month = logParts[2];
+ var day = logParts[3];
+ var time = logParts[4];
+ var treadId = parseInt(logParts[5]);
+ var file = logParts[6];
+ var fileLine = parseInt(logParts[7]);
+ var message = logParts[8];
+
+ var now = new Date();
+ var year = now.getFullYear();
+ var thisMonth = now.getMonth() + 1; // JS months are 0-11
+ // Year flip edge case, if log month > this month, we assume log line is from previous year
+ if(parseInt(month) > thisMonth) {
+ year--;
+ }
+
+ var date = new Date(year + '-' + month + '-' + day + ' ' + time);
+
+ return new item(
+ levelCodes[L],
+ date,
+ treadId,
+ file,
+ fileLine,
+ message
+ );
+}
+
+var levelCodes = {
+ 'I': 'info',
+ 'W': 'warning',
+ 'E': 'error',
+ 'F': 'fatal'
+}
+
+/*
+ * A structure representing a veyron log item
+ * @param {string} level, one of info, warning, error, fatal
+ * @param {date} date, The date and time of the log item
+ * @param {integer} threadId The thread ID as returned by GetTID()
+ * @param {string} file The file name
+ * @param {integer} fileLine The file line number
+ * @param {string} message The user-supplied message
+ * @class
+ * @private
+ */
+class item {
+ constructor(level, date, threadId, file, fileLine, message) {
+ this.level = level;
+ this.date = date;
+ this.threadId = threadId;
+ this.file = file;
+ this.fileLine = fileLine;
+ this.message = message;
+ }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
new file mode 100644
index 0000000..e7dc5af
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -0,0 +1,43 @@
+/*
+ * vlog is a Pipe Viewer that displays veyron logs in a graphical grid.
+ * Please note that Veyron writes logs to stderr stream, in *nix systems 2>&1
+ * can be used to redirect stderr to stdout which can be then piped to P2B.
+ * @tutorial myVeyronServerd -v=3 2>&1 | p2b google/p2b/[name]/vlog
+ * @tutorial cat logfile.text | p2b google/p2b/[name]/vlog
+ * @fileoverview
+ */
+import { View } from 'view';
+import { PipeViewer } from 'pipe-viewer';
+
+import { parse } from './parser';
+
+var streamUtil = require('event-stream');
+
+class vLogPipeViewer extends PipeViewer {
+ get name() {
+ return 'vlog';
+ }
+
+ play(stream) {
+ stream.setEncoding('utf8');
+
+ // split by new line
+ stream = stream.pipe(streamUtil.split(/\r?\n/));
+
+ var logItems = [];
+ var logView = document.createElement('p2b-plugin-vlog');
+ logView.logItems = logItems;
+
+ stream.on('data', (line) => {
+ // try to parse and display as much as we can.
+ try {
+ logItems.push( parse(line) );
+ } catch(e) {}
+
+ });
+
+ return new View(logView);
+ }
+}
+
+export default vLogPipeViewer;
\ No newline at end of file
diff --git a/examples/pipetobrowser/p2b/main.go b/examples/pipetobrowser/p2b/main.go
index d368b17..c2f500c 100644
--- a/examples/pipetobrowser/p2b/main.go
+++ b/examples/pipetobrowser/p2b/main.go
@@ -23,12 +23,21 @@
For example:
- ls -l | p2b pipetobrowser/jane/DataTable
+ ls -l | p2b google/p2b/jane/console
- where <name> (pipetobrowser/jane) is the veyron name where p2b
- service is running in the browser. <viewer> (DataTable) specifies what
+ or
+
+ cat cat.jpg | google/p2b/jane/image
+
+ where <name> (google/p2b/jane) is the veyron name where p2b
+ service is running in the browser. <viewer> (console, image) specifies what
viewer should be used to display the data.
+ To redirect stderr of a process, in *nix system you can use 2>&1 before piping to P2B.
+
+ For example many daemons may write log lines to stderr instead of stdout:
+
+ serverd -alsologtostderr=true 2>&1 | google/p2b/jane/vlog
`
func Usage() {
diff --git a/runtimes/google/ipc/benchmarks/README.txt b/runtimes/google/ipc/benchmarks/README.txt
new file mode 100644
index 0000000..861911b
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/README.txt
@@ -0,0 +1,130 @@
+This directory contains code uses to measure the performance of the Veyron IPC
+stack.
+
+The ipc_test.go file uses GO's testing package to run benchmarks. Each
+benchmark involves one server and one client. The server has two very simple
+methods that echo the data received from the client back to the client.
+
+client ---- Echo(payload) ----> server
+client <--- return payload ---- server
+
+There are two versions of the Echo method:
+ - Echo(payload []byte) ([]byte], error)
+ - EchoStream() <[]byte,[]byte> error
+
+The first benchmarks use the non-streaming version of Echo with a varying
+payload size. The other benchmarks use the streaming version with varying
+number of chunks and payload sizes.
+
+All these benchmarks create a VC before measurements begin. So, the VC creation
+overhead is excluded.
+
+On a ThinkPad X1 Carbon (2 × Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz), we get:
+
+$ $VEYRON_ROOT/veyron/scripts/build/go test -test.bench=. -test.cpu=1 \
+ -test.benchtime=5s veyron/runtimes/google/ipc/benchmarks 2> /dev/null
+PASS
+Benchmark____1B 10000 545077 ns/op
+Benchmark___10B 10000 587312 ns/op
+Benchmark__100B 10000 523019 ns/op
+Benchmark___1KB 10000 605235 ns/op
+Benchmark__10KB 10000 957467 ns/op
+Benchmark_100KB 5000 4101891 ns/op
+Benchmark_N_RPCs____1_chunk_____1B 10000 554063 ns/op
+Benchmark_N_RPCs____1_chunk____10B 10000 551424 ns/op
+Benchmark_N_RPCs____1_chunk___100B 10000 538308 ns/op
+Benchmark_N_RPCs____1_chunk____1KB 10000 585579 ns/op
+Benchmark_N_RPCs____1_chunk___10KB 10000 904789 ns/op
+Benchmark_N_RPCs___10_chunks___1KB 10000 1460984 ns/op
+Benchmark_N_RPCs__100_chunks___1KB 1000 8491514 ns/op
+Benchmark_N_RPCs_1000_chunks___1KB 100 105269359 ns/op
+Benchmark_1_RPC_N_chunks_____1B 200000 763769 ns/op
+Benchmark_1_RPC_N_chunks____10B 100000 583134 ns/op
+Benchmark_1_RPC_N_chunks___100B 100000 80849 ns/op
+Benchmark_1_RPC_N_chunks____1KB 100000 88820 ns/op
+Benchmark_1_RPC_N_chunks___10KB 50000 361596 ns/op
+Benchmark_1_RPC_N_chunks__100KB 5000 3127193 ns/op
+ok veyron/runtimes/google/ipc/benchmarks 525.095s
+
+
+The Benchmark_Simple_____1KB line shows that it takes an average of 0.605 ms to
+execute a simple Echo RPC with a 1 KB payload.
+
+The Benchmark_N_RPCs____1_chunk____1KB line shows that a streaming RPC with the
+same payload (i.e. 1 chunk of 1 KB) takes about 0.586 ms on average.
+
+And Benchmark_1_RPC_N_chunks____1KB shows that sending a stream of 1 KB chunks
+takes an average of 0.088 ms per chunk.
+
+
+Running the client and server as separate processes.
+
+In this case, we can see the cost of name resolution, creating the VC, etc. in
+the first RPC.
+
+$ $VEYRON_ROOT/veyron/go/bin/bmserver --address=localhost:8888 --acl='{"*":"A"}'
+
+(In a different shell)
+$ $VEYRON_ROOT/veyron/go/bin/bmclient --server=/localhost:8888 --count=10 \
+ --payload_size=1000
+CallEcho 0 64133467
+CallEcho 1 766223
+CallEcho 2 703860
+CallEcho 3 697590
+CallEcho 4 601134
+CallEcho 5 601142
+CallEcho 6 624079
+CallEcho 7 644664
+CallEcho 8 605195
+CallEcho 9 637037
+
+It took about 64 ms to execute the first RPC, and then 0.60-0.70 ms to execute
+the next ones.
+
+
+On a Raspberry Pi, everything is much slower. The same tests show the following
+results:
+
+$ ./benchmarks.test -test.bench=. -test.cpu=1 -test.benchtime=5s 2>/dev/null
+PASS
+Benchmark____1B 500 21316148 ns/op
+Benchmark___10B 500 23304638 ns/op
+Benchmark__100B 500 21860446 ns/op
+Benchmark___1KB 500 24000346 ns/op
+Benchmark__10KB 200 37530575 ns/op
+Benchmark_100KB 100 136243310 ns/op
+Benchmark_N_RPCs____1_chunk_____1B 500 19957506 ns/op
+Benchmark_N_RPCs____1_chunk____10B 500 22868392 ns/op
+Benchmark_N_RPCs____1_chunk___100B 500 19635412 ns/op
+Benchmark_N_RPCs____1_chunk____1KB 500 22572190 ns/op
+Benchmark_N_RPCs____1_chunk___10KB 500 37570948 ns/op
+Benchmark_N_RPCs___10_chunks___1KB 100 51670740 ns/op
+Benchmark_N_RPCs__100_chunks___1KB 50 364938740 ns/op
+Benchmark_N_RPCs_1000_chunks___1KB 2 3586374500 ns/op
+Benchmark_1_RPC_N_chunks_____1B 10000 1034042 ns/op
+Benchmark_1_RPC_N_chunks____10B 5000 1894875 ns/op
+Benchmark_1_RPC_N_chunks___100B 5000 2857289 ns/op
+Benchmark_1_RPC_N_chunks____1KB 5000 6465839 ns/op
+Benchmark_1_RPC_N_chunks___10KB 100 80019430 ns/op
+Benchmark_1_RPC_N_chunks__100KB Killed
+
+The simple 1 KB RPCs take an average of 24 ms. The streaming equivalent takes
+about 22 ms, and streaming many 1 KB chunks takes about 6.5 ms per chunk.
+
+
+$ ./bmserver --address=localhost:8888 --acl='{"*":"A"}'
+
+$ ./bmclient --server=/localhost:8888 --count=10 --payload_size=1000
+CallEcho 0 2573406000
+CallEcho 1 44669000
+CallEcho 2 54442000
+CallEcho 3 33934000
+CallEcho 4 47985000
+CallEcho 5 61324000
+CallEcho 6 51654000
+CallEcho 7 47043000
+CallEcho 8 44995000
+CallEcho 9 53166000
+
+On the pi, the first RPC takes ~2.5 sec to execute.
+
diff --git a/runtimes/google/ipc/benchmarks/bmclient/main.go b/runtimes/google/ipc/benchmarks/bmclient/main.go
new file mode 100644
index 0000000..16c34c5
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/bmclient/main.go
@@ -0,0 +1,27 @@
+// a simple command-line tool to run the benchmark client.
+package main
+
+import (
+ "flag"
+ "os"
+
+ "veyron/runtimes/google/ipc/benchmarks"
+
+ "veyron2/rt"
+)
+
+var (
+ server = flag.String("server", "", "veyron name of the server to connect to")
+ count = flag.Int("count", 1, "number of RPCs to send")
+ chunkCount = flag.Int("chunk_count", 0, "number of stream chunks to send")
+ payloadSize = flag.Int("payload_size", 32, "the size of the payload")
+)
+
+func main() {
+ rt.Init()
+ if *chunkCount == 0 {
+ benchmarks.CallEcho(*server, *count, *payloadSize, os.Stdout)
+ } else {
+ benchmarks.CallEchoStream(*server, *count, *chunkCount, *payloadSize, os.Stdout)
+ }
+}
diff --git a/runtimes/google/ipc/benchmarks/bmserver/main.go b/runtimes/google/ipc/benchmarks/bmserver/main.go
new file mode 100644
index 0000000..017ad69
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/bmserver/main.go
@@ -0,0 +1,25 @@
+// a simple command-line tool to run the benchmark server.
+package main
+
+import (
+ "flag"
+
+ "veyron/lib/signals"
+ "veyron/runtimes/google/ipc/benchmarks"
+
+ "veyron2/rt"
+ "veyron2/vlog"
+)
+
+var (
+ address = flag.String("address", ":0", "address to listen on")
+ protocol = flag.String("protocol", "tcp", "protocol to listen on")
+)
+
+func main() {
+ rt.Init()
+ addr, stop := benchmarks.StartServer(*protocol, *address)
+ vlog.Infof("Listening on %s", addr)
+ defer stop()
+ <-signals.ShutdownOnSignals()
+}
diff --git a/runtimes/google/ipc/benchmarks/client.go b/runtimes/google/ipc/benchmarks/client.go
new file mode 100644
index 0000000..8117e8f
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/client.go
@@ -0,0 +1,101 @@
+package benchmarks
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "time"
+
+ "veyron2"
+ "veyron2/rt"
+ "veyron2/vlog"
+)
+
+// CallEcho calls the Echo method 'iterations' times with the given payload
+// size, and optionally logs the result.
+func CallEcho(address string, iterations, payloadSize int, log io.Writer) {
+ payload := make([]byte, payloadSize)
+ for _, i := range payload {
+ payload[i] = byte(i & 0xff)
+ }
+
+ stub, err := BindBenchmark(address)
+ if err != nil {
+ vlog.Fatalf("BindBenchmark(%q) failed: %v", address, err)
+ }
+
+ for i := 0; i < iterations; i++ {
+ start := time.Now()
+ result, err := stub.Echo(rt.R().TODOContext(), payload)
+ elapsed := time.Since(start)
+ if err != nil {
+ vlog.Fatalf("Echo failed: %v", err)
+ }
+ if !bytes.Equal(payload, result) {
+ vlog.Fatalf("Echo return different payload: got %v, expected %v", result, payload)
+ }
+ if log != nil {
+ log.Write([]byte(fmt.Sprintf("CallEcho %d %d\n", i, elapsed)))
+ }
+ }
+}
+
+// CallEchoStream calls the EchoStream method 'rpcCount' times. Each iteration
+// sends 'messageCount' messages on the stream and receives the same number
+// back. Each message has the given payload size. Optionally logs the result.
+func CallEchoStream(address string, rpcCount, messageCount, payloadSize int, log io.Writer) {
+ payload := make([]byte, payloadSize)
+ for _, i := range payload {
+ payload[i] = byte(i & 0xff)
+ }
+
+ stub, err := BindBenchmark(address)
+ if err != nil {
+ vlog.Fatalf("BindBenchmark(%q) failed: %v", address, err)
+ }
+
+ for i := 0; i < rpcCount; i++ {
+ start := time.Now()
+ stream, err := stub.EchoStream(rt.R().TODOContext(), veyron2.CallTimeout(time.Hour))
+ if err != nil {
+ vlog.Fatalf("EchoStream failed: %v", err)
+ }
+ done := make(chan error, 1)
+ go func() {
+ for {
+ chunk, err := stream.Recv()
+ if err == io.EOF {
+ done <- nil
+ return
+ }
+ if err != nil {
+ done <- err
+ return
+ }
+ if !bytes.Equal(payload, chunk) {
+ done <- fmt.Errorf("Recv got different payload: got %v, expected %v", chunk, payload)
+ return
+ }
+ }
+ }()
+ for j := 0; j < messageCount; j++ {
+ if err = stream.Send(payload); err != nil {
+ vlog.Fatalf("Send failed: %v", err)
+ }
+ }
+ if err = stream.CloseSend(); err != nil {
+ vlog.Fatalf("CloseSend() failed: %v", err)
+ }
+ if err = <-done; err != nil {
+ vlog.Fatalf("%v", err)
+ }
+
+ if err = stream.Finish(); err != nil {
+ vlog.Fatalf("Finish failed: %v", err)
+ }
+ elapsed := time.Since(start)
+ if log != nil {
+ log.Write([]byte(fmt.Sprintf("CallEchoStream %d %d\n", i, elapsed)))
+ }
+ }
+}
diff --git a/runtimes/google/ipc/benchmarks/ipc_test.go b/runtimes/google/ipc/benchmarks/ipc_test.go
new file mode 100644
index 0000000..411869f
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/ipc_test.go
@@ -0,0 +1,109 @@
+package benchmarks_test
+
+import (
+ "testing"
+
+ "veyron/runtimes/google/ipc/benchmarks"
+
+ "veyron2/rt"
+)
+
+func init() {
+ rt.Init()
+}
+
+func RunBenchmark(b *testing.B, payloadSize int) {
+ address, stop := benchmarks.StartServer("tcp", "127.0.0.1:0")
+ defer stop()
+ benchmarks.CallEcho(address, 1, 1, nil) // Create VC
+ b.ResetTimer()
+ benchmarks.CallEcho(address, b.N, payloadSize, nil)
+}
+
+func RunStreamBenchmark(b *testing.B, rpcCount, messageCount, payloadSize int) {
+ address, stop := benchmarks.StartServer("tcp", "127.0.0.1:0")
+ defer stop()
+ benchmarks.CallEchoStream(address, 1, 1, 1, nil) // Create VC
+ b.ResetTimer()
+ benchmarks.CallEchoStream(address, rpcCount, messageCount, payloadSize, nil)
+}
+
+func Benchmark____1B(b *testing.B) {
+ RunBenchmark(b, 1)
+}
+
+func Benchmark___10B(b *testing.B) {
+ RunBenchmark(b, 10)
+}
+
+func Benchmark__100B(b *testing.B) {
+ RunBenchmark(b, 100)
+}
+
+func Benchmark___1KB(b *testing.B) {
+ RunBenchmark(b, 1000)
+}
+
+func Benchmark__10KB(b *testing.B) {
+ RunBenchmark(b, 10000)
+}
+
+func Benchmark_100KB(b *testing.B) {
+ RunBenchmark(b, 100000)
+}
+
+func Benchmark_N_RPCs____1_chunk_____1B(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1, 1)
+}
+
+func Benchmark_N_RPCs____1_chunk____10B(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1, 10)
+}
+
+func Benchmark_N_RPCs____1_chunk___100B(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1, 100)
+}
+
+func Benchmark_N_RPCs____1_chunk____1KB(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1, 1000)
+}
+
+func Benchmark_N_RPCs____1_chunk___10KB(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1, 10000)
+}
+
+func Benchmark_N_RPCs___10_chunks___1KB(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 10, 1000)
+}
+
+func Benchmark_N_RPCs__100_chunks___1KB(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 100, 1000)
+}
+
+func Benchmark_N_RPCs_1000_chunks___1KB(b *testing.B) {
+ RunStreamBenchmark(b, b.N, 1000, 1000)
+}
+
+func Benchmark_1_RPC_N_chunks_____1B(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 1)
+}
+
+func Benchmark_1_RPC_N_chunks____10B(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 10)
+}
+
+func Benchmark_1_RPC_N_chunks___100B(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 100)
+}
+
+func Benchmark_1_RPC_N_chunks____1KB(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 1000)
+}
+
+func Benchmark_1_RPC_N_chunks___10KB(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 10000)
+}
+
+func Benchmark_1_RPC_N_chunks__100KB(b *testing.B) {
+ RunStreamBenchmark(b, 1, b.N, 100000)
+}
diff --git a/runtimes/google/ipc/benchmarks/server.go b/runtimes/google/ipc/benchmarks/server.go
new file mode 100644
index 0000000..d585d72
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/server.go
@@ -0,0 +1,57 @@
+package benchmarks
+
+import (
+ "io"
+
+ sflag "veyron/security/flag"
+
+ "veyron2/ipc"
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/vlog"
+)
+
+type impl struct {
+}
+
+func (i *impl) Echo(ctx ipc.ServerContext, payload []byte) ([]byte, error) {
+ return payload, nil
+}
+
+func (i *impl) EchoStream(ctx ipc.ServerContext, stream BenchmarkServiceEchoStreamStream) error {
+ for {
+ chunk, err := stream.Recv()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ if err := stream.Send(chunk); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// StartServer starts a server that implements the Benchmark service. The
+// server listens to the given protocol and address, and returns the veyron
+// address of the server and a callback function to stop the server.
+func StartServer(protocol, address string) (string, func()) {
+ server, err := rt.R().NewServer()
+ if err != nil {
+ vlog.Fatalf("NewServer failed: %v", err)
+ }
+ if err := server.Register("", ipc.SoloDispatcher(NewServerBenchmark(&impl{}), sflag.NewAuthorizerOrDie())); err != nil {
+ vlog.Fatalf("Register failed: %v", err)
+ }
+ ep, err := server.Listen(protocol, address)
+ if err != nil {
+ vlog.Fatalf("Listen failed: %v", err)
+ }
+ return naming.JoinAddressName(ep.String(), ""), func() {
+ if err := server.Stop(); err != nil {
+ vlog.Fatalf("Stop() failed: %v", err)
+ }
+ }
+}
diff --git a/runtimes/google/ipc/benchmarks/service.vdl b/runtimes/google/ipc/benchmarks/service.vdl
new file mode 100644
index 0000000..ad0afcf
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/service.vdl
@@ -0,0 +1,10 @@
+// package benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmarks
+
+type Benchmark interface {
+ // Echo returns the payload that it receives.
+ Echo(Payload []byte) ([]byte, error)
+ // EchoStream returns the payload that it receives via the stream.
+ EchoStream() stream<[]byte,[]byte> error
+}
diff --git a/runtimes/google/ipc/benchmarks/service.vdl.go b/runtimes/google/ipc/benchmarks/service.vdl.go
new file mode 100644
index 0000000..371285d
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/service.vdl.go
@@ -0,0 +1,293 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: service.vdl
+
+// package benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmarks
+
+import (
+ // The non-user imports are prefixed with "_gen_" to prevent collisions.
+ _gen_veyron2 "veyron2"
+ _gen_context "veyron2/context"
+ _gen_ipc "veyron2/ipc"
+ _gen_naming "veyron2/naming"
+ _gen_rt "veyron2/rt"
+ _gen_vdl "veyron2/vdl"
+ _gen_wiretype "veyron2/wiretype"
+)
+
+// Benchmark is the interface the client binds and uses.
+// Benchmark_ExcludingUniversal is the interface without internal framework-added methods
+// to enable embedding without method collisions. Not to be used directly by clients.
+type Benchmark_ExcludingUniversal interface {
+ // Echo returns the payload that it receives.
+ Echo(ctx _gen_context.T, Payload []byte, opts ..._gen_ipc.CallOpt) (reply []byte, err error)
+ // EchoStream returns the payload that it receives via the stream.
+ EchoStream(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply BenchmarkEchoStreamStream, err error)
+}
+type Benchmark interface {
+ _gen_ipc.UniversalServiceMethods
+ Benchmark_ExcludingUniversal
+}
+
+// BenchmarkService is the interface the server implements.
+type BenchmarkService interface {
+
+ // Echo returns the payload that it receives.
+ Echo(context _gen_ipc.ServerContext, Payload []byte) (reply []byte, err error)
+ // EchoStream returns the payload that it receives via the stream.
+ EchoStream(context _gen_ipc.ServerContext, stream BenchmarkServiceEchoStreamStream) (err error)
+}
+
+// BenchmarkEchoStreamStream is the interface for streaming responses of the method
+// EchoStream in the service interface Benchmark.
+type BenchmarkEchoStreamStream interface {
+
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item []byte) error
+
+ // CloseSend indicates to the server that no more items will be sent; server
+ // Recv calls will receive io.EOF after all sent items. Subsequent calls to
+ // Send on the client will fail. This is an optional call - it's used by
+ // streaming clients that need the server to receive the io.EOF terminator.
+ CloseSend() error
+
+ // Recv returns the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item []byte, err error)
+
+ // Finish closes the stream and returns the positional return values for
+ // call.
+ Finish() (err error)
+
+ // Cancel cancels the RPC, notifying the server to stop processing.
+ Cancel()
+}
+
+// Implementation of the BenchmarkEchoStreamStream interface that is not exported.
+type implBenchmarkEchoStreamStream struct {
+ clientCall _gen_ipc.Call
+}
+
+func (c *implBenchmarkEchoStreamStream) Send(item []byte) error {
+ return c.clientCall.Send(item)
+}
+
+func (c *implBenchmarkEchoStreamStream) CloseSend() error {
+ return c.clientCall.CloseSend()
+}
+
+func (c *implBenchmarkEchoStreamStream) Recv() (item []byte, err error) {
+ err = c.clientCall.Recv(&item)
+ return
+}
+
+func (c *implBenchmarkEchoStreamStream) Finish() (err error) {
+ if ierr := c.clientCall.Finish(&err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (c *implBenchmarkEchoStreamStream) Cancel() {
+ c.clientCall.Cancel()
+}
+
+// BenchmarkServiceEchoStreamStream is the interface for streaming responses of the method
+// EchoStream in the service interface Benchmark.
+type BenchmarkServiceEchoStreamStream interface {
+ // Send places the item onto the output stream, blocking if there is no buffer
+ // space available.
+ Send(item []byte) error
+
+ // Recv fills itemptr with the next item in the input stream, blocking until
+ // an item is available. Returns io.EOF to indicate graceful end of input.
+ Recv() (item []byte, err error)
+}
+
+// Implementation of the BenchmarkServiceEchoStreamStream interface that is not exported.
+type implBenchmarkServiceEchoStreamStream struct {
+ serverCall _gen_ipc.ServerCall
+}
+
+func (s *implBenchmarkServiceEchoStreamStream) Send(item []byte) error {
+ return s.serverCall.Send(item)
+}
+
+func (s *implBenchmarkServiceEchoStreamStream) Recv() (item []byte, err error) {
+ err = s.serverCall.Recv(&item)
+ return
+}
+
+// BindBenchmark returns the client stub implementing the Benchmark
+// interface.
+//
+// If no _gen_ipc.Client is specified, the default _gen_ipc.Client in the
+// global Runtime is used.
+func BindBenchmark(name string, opts ..._gen_ipc.BindOpt) (Benchmark, error) {
+ var client _gen_ipc.Client
+ switch len(opts) {
+ case 0:
+ client = _gen_rt.R().Client()
+ case 1:
+ switch o := opts[0].(type) {
+ case _gen_veyron2.Runtime:
+ client = o.Client()
+ case _gen_ipc.Client:
+ client = o
+ default:
+ return nil, _gen_vdl.ErrUnrecognizedOption
+ }
+ default:
+ return nil, _gen_vdl.ErrTooManyOptionsToBind
+ }
+ stub := &clientStubBenchmark{client: client, name: name}
+
+ return stub, nil
+}
+
+// NewServerBenchmark creates a new server stub.
+//
+// It takes a regular server implementing the BenchmarkService
+// interface, and returns a new server stub.
+func NewServerBenchmark(server BenchmarkService) interface{} {
+ return &ServerStubBenchmark{
+ service: server,
+ }
+}
+
+// clientStubBenchmark implements Benchmark.
+type clientStubBenchmark struct {
+ client _gen_ipc.Client
+ name string
+}
+
+func (__gen_c *clientStubBenchmark) Echo(ctx _gen_context.T, Payload []byte, opts ..._gen_ipc.CallOpt) (reply []byte, err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Echo", []interface{}{Payload}, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (__gen_c *clientStubBenchmark) EchoStream(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply BenchmarkEchoStreamStream, err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "EchoStream", nil, opts...); err != nil {
+ return
+ }
+ reply = &implBenchmarkEchoStreamStream{clientCall: call}
+ return
+}
+
+func (__gen_c *clientStubBenchmark) UnresolveStep(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply []string, err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "UnresolveStep", nil, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (__gen_c *clientStubBenchmark) Signature(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply _gen_ipc.ServiceSignature, err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Signature", nil, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (__gen_c *clientStubBenchmark) GetMethodTags(ctx _gen_context.T, method string, opts ..._gen_ipc.CallOpt) (reply []interface{}, err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "GetMethodTags", []interface{}{method}, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&reply, &err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+// ServerStubBenchmark wraps a server that implements
+// BenchmarkService and provides an object that satisfies
+// the requirements of veyron2/ipc.ReflectInvoker.
+type ServerStubBenchmark struct {
+ service BenchmarkService
+}
+
+func (__gen_s *ServerStubBenchmark) GetMethodTags(call _gen_ipc.ServerCall, method string) ([]interface{}, error) {
+ // TODO(bprosnitz) GetMethodTags() will be replaces with Signature().
+ // Note: This exhibits some weird behavior like returning a nil error if the method isn't found.
+ // This will change when it is replaced with Signature().
+ switch method {
+ case "Echo":
+ return []interface{}{}, nil
+ case "EchoStream":
+ return []interface{}{}, nil
+ default:
+ return nil, nil
+ }
+}
+
+func (__gen_s *ServerStubBenchmark) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
+ result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
+ result.Methods["Echo"] = _gen_ipc.MethodSignature{
+ InArgs: []_gen_ipc.MethodArgument{
+ {Name: "Payload", Type: 66},
+ },
+ OutArgs: []_gen_ipc.MethodArgument{
+ {Name: "", Type: 66},
+ {Name: "", Type: 67},
+ },
+ }
+ result.Methods["EchoStream"] = _gen_ipc.MethodSignature{
+ InArgs: []_gen_ipc.MethodArgument{},
+ OutArgs: []_gen_ipc.MethodArgument{
+ {Name: "", Type: 67},
+ },
+ InStream: 66,
+ OutStream: 66,
+ }
+
+ result.TypeDefs = []_gen_vdl.Any{
+ _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x41, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
+
+ return result, nil
+}
+
+func (__gen_s *ServerStubBenchmark) UnresolveStep(call _gen_ipc.ServerCall) (reply []string, err error) {
+ if unresolver, ok := __gen_s.service.(_gen_ipc.Unresolver); ok {
+ return unresolver.UnresolveStep(call)
+ }
+ if call.Server() == nil {
+ return
+ }
+ var published []string
+ if published, err = call.Server().Published(); err != nil || published == nil {
+ return
+ }
+ reply = make([]string, len(published))
+ for i, p := range published {
+ reply[i] = _gen_naming.Join(p, call.Name())
+ }
+ return
+}
+
+func (__gen_s *ServerStubBenchmark) Echo(call _gen_ipc.ServerCall, Payload []byte) (reply []byte, err error) {
+ reply, err = __gen_s.service.Echo(call, Payload)
+ return
+}
+
+func (__gen_s *ServerStubBenchmark) EchoStream(call _gen_ipc.ServerCall) (err error) {
+ stream := &implBenchmarkServiceEchoStreamStream{serverCall: call}
+ err = __gen_s.service.EchoStream(call, stream)
+ return
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index f1c8718..fb4406d 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -370,9 +370,7 @@
}
acl := make(security.ACL)
for _, n := range id.Names() {
- if !strings.HasPrefix(n, wire.UntrustedIDProviderPrefix) {
- acl[security.PrincipalPattern(n+wire.ChainSeparator+security.AllPrincipals)] = security.AllLabels
- }
+ acl[security.PrincipalPattern(n+wire.ChainSeparator+security.AllPrincipals)] = security.AllLabels
}
return acl
}
diff --git a/runtimes/google/ipc/stream/crypto/box.go b/runtimes/google/ipc/stream/crypto/box.go
new file mode 100644
index 0000000..3ab2c33
--- /dev/null
+++ b/runtimes/google/ipc/stream/crypto/box.go
@@ -0,0 +1,87 @@
+package crypto
+
+import (
+ "bytes"
+ "code.google.com/p/go.crypto/nacl/box"
+ "crypto/rand"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "net"
+ "veyron/runtimes/google/lib/iobuf"
+)
+
+type boxcrypter struct {
+ conn net.Conn
+ alloc *iobuf.Allocator
+ sharedKey [32]byte
+ writeNonce, readNonce uint64
+}
+
+// NewBoxCrypter uses Curve25519, XSalsa20 and Poly1305 to encrypt and
+// authenticate messages (as defined in http://nacl.cr.yp.to/box.html).
+// An ephemeral Diffie-Hellman key exchange is performed per invocation
+// of NewBoxCrypter; the data sent has forward security with connection
+// granularity. One round-trip is required before any data can be sent.
+// BoxCrypter does NOT do anything to verify the identity of the peer.
+func NewBoxCrypter(conn net.Conn, pool *iobuf.Pool) (Crypter, error) {
+ pk, sk, err := box.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ var theirPK [32]byte
+ errChan := make(chan error)
+ defer close(errChan)
+ go func() { _, err := conn.Write(pk[:]); errChan <- err }()
+ go func() { _, err := io.ReadFull(conn, theirPK[:]); errChan <- err }()
+ if err := <-errChan; err != nil {
+ return nil, err
+ }
+ if err := <-errChan; err != nil {
+ return nil, err
+ }
+ ret := &boxcrypter{conn: conn, alloc: iobuf.NewAllocator(pool, 0)}
+ box.Precompute(&ret.sharedKey, &theirPK, sk)
+ // Distinct messages between the same {sender, receiver} set are required
+ // to have distinct nonces. The server with the lexicographically smaller
+ // public key will be sending messages with 0, 2, 4... and the other will
+ // be using 1, 3, 5...
+ if bytes.Compare(pk[:], theirPK[:]) < 0 {
+ ret.writeNonce, ret.readNonce = 0, 1
+ } else {
+ ret.writeNonce, ret.readNonce = 1, 0
+ }
+ return ret, nil
+}
+
+func (c *boxcrypter) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+ defer src.Release()
+ var nonce [24]byte
+ binary.LittleEndian.PutUint64(nonce[:], c.writeNonce)
+ c.writeNonce += 2
+ ret := c.alloc.Alloc(uint(len(src.Contents) + box.Overhead))
+ ret.Contents = box.SealAfterPrecomputation(ret.Contents[:0], src.Contents, &nonce, &c.sharedKey)
+ return ret, nil
+}
+
+func (c *boxcrypter) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+ defer src.Release()
+ var nonce [24]byte
+ binary.LittleEndian.PutUint64(nonce[:], c.readNonce)
+ c.readNonce += 2
+ retLen := len(src.Contents) - box.Overhead
+ if retLen < 0 {
+ return nil, fmt.Errorf("ciphertext too short")
+ }
+ ret := c.alloc.Alloc(uint(retLen))
+ var ok bool
+ ret.Contents, ok = box.OpenAfterPrecomputation(ret.Contents[:0], src.Contents, &nonce, &c.sharedKey)
+ if !ok {
+ return nil, fmt.Errorf("message authentication failed")
+ }
+ return ret, nil
+}
+
+func (c *boxcrypter) String() string {
+ return fmt.Sprintf("%#v", *c)
+}
diff --git a/runtimes/google/ipc/stream/crypto/crypto_test.go b/runtimes/google/ipc/stream/crypto/crypto_test.go
index 063d222..4bbdd45 100644
--- a/runtimes/google/ipc/stream/crypto/crypto_test.go
+++ b/runtimes/google/ipc/stream/crypto/crypto_test.go
@@ -37,8 +37,8 @@
crypter.String() // Only to test that String does not crash.
}
-func TestTLS(t *testing.T) {
- c1, c2 := tlsCrypters(t)
+func testSimple(t *testing.T, crypters func(testing.TB) (Crypter, Crypter)) {
+ c1, c2 := crypters(t)
// Execute String just to check that it does not crash.
c1.String()
c2.String()
@@ -63,6 +63,9 @@
t.Logf("Byte overhead of encryption: %v", overhead)
}
+func TestTLS(t *testing.T) { testSimple(t, tlsCrypters) }
+func TestBox(t *testing.T) { testSimple(t, boxCrypters) }
+
func TestTLSNil(t *testing.T) {
c1, c2 := tlsCrypters(t)
if t.Failed() {
@@ -123,12 +126,27 @@
return c1, c2
}
-func benchmarkEncrypt(b *testing.B, size int) {
+func boxCrypters(t testing.TB) (Crypter, Crypter) {
+ serverConn, clientConn := net.Pipe()
+ crypters := make(chan Crypter)
+ for _, conn := range []net.Conn{serverConn, clientConn} {
+ go func(conn net.Conn) {
+ crypter, err := NewBoxCrypter(conn, iobuf.NewPool(0))
+ if err != nil {
+ t.Fatal(err)
+ }
+ crypters <- crypter
+ }(conn)
+ }
+ return <-crypters, <-crypters
+}
+
+func benchmarkEncrypt(b *testing.B, crypters func(testing.TB) (Crypter, Crypter), size int) {
plaintext := make([]byte, size)
if _, err := rand.Read(plaintext); err != nil {
b.Fatal(err)
}
- e, _ := tlsCrypters(b)
+ e, _ := crypters(b)
b.SetBytes(int64(size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -140,13 +158,19 @@
}
}
-func BenchmarkEncrypt_1B(b *testing.B) { benchmarkEncrypt(b, 1) }
-func BenchmarkEncrypt_1K(b *testing.B) { benchmarkEncrypt(b, 1<<10) }
-func BenchmarkEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, 10<<10) }
-func BenchmarkEncrypt_1M(b *testing.B) { benchmarkEncrypt(b, 1<<20) }
-func BenchmarkEncrypt_5M(b *testing.B) { benchmarkEncrypt(b, 5<<20) }
+func BenchmarkTLSEncrypt_1B(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 1) }
+func BenchmarkTLSEncrypt_1K(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 1<<10) }
+func BenchmarkTLSEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 10<<10) }
+func BenchmarkTLSEncrypt_1M(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 1<<20) }
+func BenchmarkTLSEncrypt_5M(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 5<<20) }
-func benchmarkRoundTrip(b *testing.B, size int) {
+func BenchmarkBoxEncrypt_1B(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 1) }
+func BenchmarkBoxEncrypt_1K(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 1<<10) }
+func BenchmarkBoxEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 10<<10) }
+func BenchmarkBoxEncrypt_1M(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 1<<20) }
+func BenchmarkBoxEncrypt_5M(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 5<<20) }
+
+func benchmarkRoundTrip(b *testing.B, crypters func(testing.TB) (Crypter, Crypter), size int) {
plaintext := make([]byte, size)
if _, err := rand.Read(plaintext); err != nil {
b.Fatal(err)
@@ -166,8 +190,14 @@
plainslice.Release()
}
}
-func BenchmarkRoundTrip_1B(b *testing.B) { benchmarkRoundTrip(b, 1) }
-func BenchmarkRoundTrip_1K(b *testing.B) { benchmarkRoundTrip(b, 1<<10) }
-func BenchmarkRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, 10<<10) }
-func BenchmarkRoundTrip_1M(b *testing.B) { benchmarkRoundTrip(b, 1<<20) }
-func BenchmarkRoundTrip_5M(b *testing.B) { benchmarkRoundTrip(b, 5<<20) }
+func BenchmarkTLSRoundTrip_1B(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 1) }
+func BenchmarkTLSRoundTrip_1K(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 1<<10) }
+func BenchmarkTLSRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 10<<10) }
+func BenchmarkTLSRoundTrip_1M(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 1<<20) }
+func BenchmarkTLSRoundTrip_5M(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 5<<20) }
+
+func BenchmarkBoxRoundTrip_1B(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 1) }
+func BenchmarkBoxRoundTrip_1K(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 1<<10) }
+func BenchmarkBoxRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 10<<10) }
+func BenchmarkBoxRoundTrip_1M(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 1<<20) }
+func BenchmarkBoxRoundTrip_5M(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 5<<20) }
diff --git a/runtimes/google/security/identity_chain.go b/runtimes/google/security/identity_chain.go
index b8439e0..b6ad17b 100644
--- a/runtimes/google/security/identity_chain.go
+++ b/runtimes/google/security/identity_chain.go
@@ -19,6 +19,17 @@
"veyron2/vom"
)
+const (
+ // unknownIDProviderPrefix is the prefix added when stringifying
+ // an identity for which there is no entry for the root certificate
+ // in the trusted keys set.
+ unknownIDProviderPrefix = "unknown/"
+ // mistrustedIDProviderPrefix is the prefix added when stringifying
+ // an identity whose root certificate has a public key that does
+ // not exist in the (non-empty) set of trusted keys for that root.
+ mistrustedIDProviderPrefix = "mistrusted/"
+)
+
// chainPublicID implements security.PublicID.
type chainPublicID struct {
certificates []wire.Certificate
@@ -50,10 +61,14 @@
func (id *chainPublicID) String() string {
// Add a prefix if the identity provider is not trusted.
- if keys.LevelOfTrust(id.rootKey, id.certificates[0].Name) != keys.Trusted {
- return wire.UntrustedIDProviderPrefix + id.name
+ switch keys.LevelOfTrust(id.rootKey, id.certificates[0].Name) {
+ case keys.Trusted:
+ return id.name
+ case keys.Mistrusted:
+ return mistrustedIDProviderPrefix + id.name
+ default:
+ return unknownIDProviderPrefix + id.name
}
- return id.name
}
func (id *chainPublicID) VomEncode() (*wire.ChainPublicID, error) {
@@ -80,24 +95,10 @@
}
// Authorize checks if all caveats on the PublicID validate with respect to the
-// provided context and that the identity provider (root public key) is not
-// mistrusted. If so returns the original PublicID. This method assumes that
+// provided context and if so returns the original PublicID. This method assumes that
// the existing PublicID was obtained after successfully decoding a serialized
// PublicID and hence has integrity.
func (id *chainPublicID) Authorize(context security.Context) (security.PublicID, error) {
- rootCert := id.certificates[0]
- rootKey, err := rootCert.PublicKey.Decode()
- if err != nil {
- // unlikely to hit this case, as chainPublicID would have integrity.
- return nil, err
- }
- // Implicit "caveat": The identity provider should not be mistrusted.
- switch tl := keys.LevelOfTrust(rootKey, rootCert.Name); tl {
- case keys.Unknown, keys.Trusted:
- // No-op
- default:
- return nil, fmt.Errorf("%v public key(%v) for identity provider %q", tl, rootKey, rootCert.Name)
- }
for _, c := range id.certificates {
if err := c.ValidateCaveats(context); err != nil {
return nil, fmt.Errorf("not authorized because %v", err)
@@ -217,6 +218,9 @@
// private key, and a single self-signed certificate specifying the provided
// name and the public key corresponding to the generated private key.
func newChainPrivateID(name string) (security.PrivateID, error) {
+ if err := wire.ValidateBlessingName(name); err != nil {
+ return nil, err
+ }
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
diff --git a/runtimes/google/security/identity_test.go b/runtimes/google/security/identity_test.go
index 5c3a141..18b43bf 100644
--- a/runtimes/google/security/identity_test.go
+++ b/runtimes/google/security/identity_test.go
@@ -16,6 +16,28 @@
type S []string
+func TestNewPrivateID(t *testing.T) {
+ testdata := []struct {
+ name, err string
+ }{
+ {"alice", ""},
+ {"alice#google", ""},
+ {"alice@google", ""},
+ {"bob.smith", ""},
+ {"", "invalid blessing name"},
+ {"/", "invalid blessing name"},
+ {"/alice", "invalid blessing name"},
+ {"alice/", "invalid blessing name"},
+ {"google/alice", "invalid blessing name"},
+ {"google/alice/bob", "invalid blessing name"},
+ }
+ for _, d := range testdata {
+ if _, err := NewPrivateID(d.name); !matchesErrorPattern(err, d.err) {
+ t.Errorf("NewPrivateID(%q): got: %s, want to match: %s", d.name, err, d.err)
+ }
+ }
+}
+
func TestNameAndAuth(t *testing.T) {
var (
cUnknownAlice = newChain("alice").PublicID()
@@ -30,11 +52,10 @@
testdata := []struct {
id security.PublicID
names []string
- err string
}{
{id: cUnknownAlice},
{id: cTrustedAlice, names: S{"veyron/alice"}},
- {id: cMistrustedAlice, err: "Mistrusted"},
+ {id: cMistrustedAlice},
{id: sAlice, names: S{"veyron/alice", "google/alice"}},
{id: sBadAlice},
{id: sGoogleAlice, names: S{"google/alice"}},
@@ -48,9 +69,6 @@
t.Errorf("%q.Authorize returned: (%v, %v), exactly one return value must be nil", d.id, authID, err)
continue
}
- if !matchesErrorPattern(err, d.err) {
- t.Errorf("%q.Authorize returned error: %v, want to match: %q", d.id, err, d.err)
- }
if err := verifyAuthorizedID(d.id, authID, d.names); err != nil {
t.Error(err)
}
diff --git a/runtimes/google/security/publicid_store.go b/runtimes/google/security/publicid_store.go
new file mode 100644
index 0000000..70dcd7d
--- /dev/null
+++ b/runtimes/google/security/publicid_store.go
@@ -0,0 +1,132 @@
+package security
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "errors"
+ "fmt"
+ "reflect"
+ "sync"
+
+ "veyron2/security"
+)
+
+var (
+ errStoreAddMismatch = errors.New("public key does not match that of existing PublicIDs in the store")
+ errNoMatchingIDs = errors.New("no matching PublicIDs")
+)
+
+func errCombine(err error) error {
+ return fmt.Errorf("could not combine matching PublicIDs: %s", err)
+}
+
+type taggedIDStore map[security.PublicID][]security.PrincipalPattern
+
+// publicIDStore implements security.PublicIDStore.
+type publicIDStore struct {
+ // store contains a set of PublicIDs mapped to a set of (peer) patterns. The patterns
+ // indicate the set of peers against whom the PublicID can be used. All PublicIDs in
+ // the store must have the same public key.
+ store taggedIDStore
+ // publicKey is the common public key of all PublicIDs held in the store.
+ publicKey *ecdsa.PublicKey
+ // defaultPattern is the default PrincipalPattern to be used to select
+ // PublicIDs from the store in absence of any other search criterea.
+ defaultPattern security.PrincipalPattern
+ mu sync.RWMutex
+}
+
+func (s *publicIDStore) addTaggedID(id security.PublicID, peerPattern security.PrincipalPattern) {
+ switch p := id.(type) {
+ case *setPublicID:
+ for _, ip := range *p {
+ s.addTaggedID(ip, peerPattern)
+ }
+ default:
+ // TODO(ataly): Should we restrict this case to just PublicIDs of type *chainPublicID?
+ s.store[id] = append(s.store[id], peerPattern)
+ }
+}
+
+func (s *publicIDStore) Add(id security.PublicID, peerPattern security.PrincipalPattern) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if s.publicKey != nil && !reflect.DeepEqual(id.PublicKey(), s.publicKey) {
+ return errStoreAddMismatch
+ }
+ if s.publicKey == nil {
+ s.publicKey = id.PublicKey()
+ }
+ s.addTaggedID(id, peerPattern)
+ return nil
+}
+
+func (s *publicIDStore) ForPeer(peer security.PublicID) (security.PublicID, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ var matchingIDs []security.PublicID
+ for id, peerPatterns := range s.store {
+ for _, peerPattern := range peerPatterns {
+ if peer.Match(peerPattern) {
+ matchingIDs = append(matchingIDs, id)
+ break
+ }
+ }
+ }
+ id, err := NewSetPublicID(matchingIDs...)
+ if err != nil {
+ return nil, errCombine(err)
+ }
+ if id == nil {
+ return nil, errNoMatchingIDs
+ }
+ return id, nil
+}
+
+func (s *publicIDStore) DefaultPublicID() (security.PublicID, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ var matchingIDs []security.PublicID
+ for id, _ := range s.store {
+ if id.Match(s.defaultPattern) {
+ matchingIDs = append(matchingIDs, id)
+ }
+ }
+ id, err := NewSetPublicID(matchingIDs...)
+ if err != nil {
+ return nil, errCombine(err)
+ }
+ if id == nil {
+ return nil, errNoMatchingIDs
+ }
+ return id, nil
+}
+
+func (s *publicIDStore) SetDefaultPrincipalPattern(pattern security.PrincipalPattern) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ // TODO(ataly, ashankar): Should we check that the pattern is well-formed?
+ s.defaultPattern = pattern
+}
+
+func (s *publicIDStore) String() string {
+ var buf bytes.Buffer
+ buf.WriteString("&publicIDStore{\n")
+ buf.WriteString(" store: {\n")
+ for id, peerPatterns := range s.store {
+ buf.WriteString(fmt.Sprintf(" %s: %s,\n", id, peerPatterns))
+ }
+ buf.WriteString(fmt.Sprintf(" },\n"))
+ buf.WriteString(fmt.Sprintf(" defaultPattern: %s,\n", s.defaultPattern))
+ buf.WriteString("}")
+ return buf.String()
+}
+
+// NewPublicIDStore returns a new security.PublicIDStore with an empty
+// set of PublicIDs, and the default pattern "*" matched by all PublicIDs.
+func NewPublicIDStore() security.PublicIDStore {
+ return &publicIDStore{
+ store: make(taggedIDStore),
+ defaultPattern: security.AllPrincipals,
+ }
+}
diff --git a/runtimes/google/security/publicid_store_test.go b/runtimes/google/security/publicid_store_test.go
new file mode 100644
index 0000000..14b2f4c
--- /dev/null
+++ b/runtimes/google/security/publicid_store_test.go
@@ -0,0 +1,141 @@
+package security
+
+import (
+ "crypto/ecdsa"
+ "reflect"
+ "testing"
+
+ "veyron2/security"
+)
+
+// verifyNamesAndPublicKey checks that the provided id has exactly the provided
+// set of names and the provided public key. If the provided set is empty then
+// the provided error must be errNoMatchingIDs.
+func verifyNamesAndPublicKey(id security.PublicID, err error, names []string, pkey *ecdsa.PublicKey) bool {
+ if id == nil {
+ return err == errNoMatchingIDs && len(names) == 0
+ }
+ idNamesMap := make(map[string]bool)
+ namesMap := make(map[string]bool)
+ for _, n := range id.Names() {
+ idNamesMap[n] = true
+ }
+ for _, n := range names {
+ namesMap[n] = true
+ }
+ return reflect.DeepEqual(idNamesMap, namesMap) && reflect.DeepEqual(id.PublicKey(), pkey)
+}
+
+func TestStoreAdd(t *testing.T) {
+ var (
+ // test principals
+ cAlice = newChain("alice")
+ cBob = newChain("bob")
+ cVeyronAlice = derive(bless(cAlice.PublicID(), veyronChain, "alice", nil), cAlice)
+
+ sAlice = newSetPublicID(cAlice.PublicID(), cVeyronAlice.PublicID())
+ )
+ s := NewPublicIDStore()
+ // First Add should succeed for any PublicID (cAlice.PublicID() below)
+ if err := s.Add(cAlice.PublicID(), "alice/*"); err != nil {
+ t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, cAlice, err)
+ }
+ // Subsequent Adds must succeed only for PublicIDs with cAlice's public key.
+ if err := s.Add(cVeyronAlice.PublicID(), "*"); err != nil {
+ t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, cVeyronAlice, err)
+ }
+ if err := s.Add(sAlice, "alice/*"); err != nil {
+ t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, sAlice, err)
+ }
+ if got, want := s.Add(cBob.PublicID(), "bob/*"), errStoreAddMismatch; got != want {
+ t.Fatalf("%q.Add(%q, ...): got: %s, want: %s", s, cBob, got, want)
+ }
+}
+
+func TestStoreGetters(t *testing.T) {
+ add := func(s security.PublicIDStore, id security.PublicID, peers security.PrincipalPattern) {
+ if err := s.Add(id, peers); err != nil {
+ t.Fatalf("Add(%q, %q) failed unexpectedly: %s", id, peers, err)
+ }
+ }
+ var (
+ // test principals
+ cAlice = newChain("alice")
+ cBob = newChain("bob")
+ cVService = newChain("vservice")
+ cGService = newChain("gservice")
+ cApp = newChain("app")
+ cVeyronService = derive(bless(cVService.PublicID(), veyronChain, "service", nil), cVService)
+ cGoogleService = derive(bless(cGService.PublicID(), googleChain, "service", nil), cGService)
+ cGoogleServiceApp = derive(bless(cApp.PublicID(), cGoogleService, "app", nil), cApp)
+ // PublicIDs for Alice's PublicIDStore
+ cGoogleAlice = bless(cAlice.PublicID(), googleChain, "alice", nil)
+ cVeyronAlice = bless(cAlice.PublicID(), veyronChain, "alice", nil)
+ cGoogleServiceAlice = bless(cAlice.PublicID(), cGoogleService, "user-42", nil)
+ cVeyronServiceAlice = bless(cAlice.PublicID(), cVeyronService, "user-24", nil)
+
+ sGoogleAlice = newSetPublicID(cGoogleServiceAlice, cGoogleAlice)
+ sAllAlice = newSetPublicID(sGoogleAlice, cVeyronAlice, cVeyronServiceAlice)
+ // TODO(ataly): Test with SetPublicIDs as well.
+ )
+
+ // Create a new PublicIDStore and add Add Alice's PublicIDs to the store.
+ s := NewPublicIDStore()
+ add(s, cGoogleAlice, "google") // use cGoogleAlice against all peers matching "google/*"
+ add(s, cGoogleAlice, "veyron") // use cGoogleAlice against all peers matching "veyron/*" as well
+ add(s, cVeyronAlice, "veyron/*") // use cVeyronAlice against peers matching "veyron/*"
+ add(s, cVeyronAlice, "google") // use cVeyronAlice against peers matching "veyron/*"
+ add(s, cVeyronServiceAlice, "veyron/service/*") // use cVeyronAlice against peers matching "veyron/service*"
+ add(s, cGoogleServiceAlice, "google/service/*") // use cGoogleServiceAlice against peers matching "google/service/*"
+ add(s, sGoogleAlice, "google/service") // use any PublicID from sGoogleAlice against peers matching "google/service"
+ add(s, sAllAlice, "veyron") // use any PublicID from sAllAlice against peers matching "veyron"
+
+ pkey := cAlice.PublicID().PublicKey()
+
+ // Test ForPeer.
+ testDataByPeer := []struct {
+ peer security.PublicID
+ names []string
+ }{
+ {veyronChain.PublicID(), []string{"google/alice", "veyron/alice", "veyron/service/user-24", "google/service/user-42"}},
+ {cVeyronService.PublicID(), []string{"veyron/alice", "veyron/service/user-24"}},
+ {googleChain.PublicID(), []string{"veyron/alice", "google/alice", "google/service/user-42"}},
+ {cGoogleService.PublicID(), []string{"google/alice", "google/service/user-42"}},
+ {cGoogleServiceApp.PublicID(), []string{"google/service/user-42"}},
+ {cBob.PublicID(), nil},
+ }
+ for _, d := range testDataByPeer {
+ if got, err := s.ForPeer(d.peer); !verifyNamesAndPublicKey(got, err, d.names, pkey) {
+ t.Errorf("%s.ForPeer(%s): got: %q, want PublicID with the exact set of names %q", s, d.peer, got, d.names)
+ }
+ }
+
+ // Test initial DefaultPublicID -- we expect a PublicID with the union of the sets of names of all
+ // PublicIDs in the store.
+ defaultNames := []string{"google/alice", "veyron/alice", "veyron/service/user-24", "google/service/user-42"}
+ if got, err := s.DefaultPublicID(); !verifyNamesAndPublicKey(got, err, defaultNames, pkey) {
+ t.Errorf("%s.DefaultPublicID(): got: %s, want PublicID with the exact set of names: %s", s, got, defaultNames)
+ }
+
+ // Test SetDefaultPrincipalPattern.
+ testDataByPrincipalPattern := []struct {
+ defaultPattern security.PrincipalPattern
+ defaultNames []string
+ }{
+ {"veyron", nil},
+ {"veyron/*", []string{"veyron/alice", "veyron/service/user-24"}},
+ {"veyron/alice", []string{"veyron/alice"}},
+ {"veyron/service/*", []string{"veyron/service/user-24"}},
+ {"google", nil},
+ {"google/*", []string{"google/alice", "google/service/user-42"}},
+ {"google/alice", []string{"google/alice"}},
+ {"google/service/*", []string{"google/service/user-42"}},
+ {"bob", nil},
+ }
+ for _, d := range testDataByPrincipalPattern {
+ s.SetDefaultPrincipalPattern(d.defaultPattern)
+ if got, err := s.DefaultPublicID(); !verifyNamesAndPublicKey(got, err, d.defaultNames, pkey) {
+ t.Errorf("%s.DefaultPublicID(): got: %s, want PublicID with the exact set of names: %s", s, got, d.defaultNames)
+ }
+ }
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index 819bf73..bcd0e5a 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -522,6 +522,10 @@
}
return errOperationFailed
}
+ // Since the resolution of mtime for files is seconds,
+ // the test sleeps for a second to make sure it can
+ // check whether the "current" symlink is updated.
+ time.Sleep(time.Second)
if err := nmClient.Revert(rt.R().NewContext()); err != nil {
if err := handle.Clean(); err != nil {
vlog.Errorf("Clean() failed: %v", err)
diff --git a/services/mounttable/lib/neighborhood.go b/services/mounttable/lib/neighborhood.go
index 4165ac3..b11588e 100644
--- a/services/mounttable/lib/neighborhood.go
+++ b/services/mounttable/lib/neighborhood.go
@@ -19,7 +19,7 @@
"code.google.com/p/mdns"
)
-const endpointPrefix = "endpoint:"
+const addressPrefix = "address:"
// neighborhood defines a set of machines on the same multicast media.
type neighborhood struct {
@@ -34,8 +34,14 @@
nh *neighborhood
}
-func getPort(e naming.Endpoint) uint16 {
- addr := e.Addr()
+func getPort(address string) uint16 {
+ epAddr, _ := naming.SplitAddressName(address)
+
+ ep, err := rt.R().NewEndpoint(epAddr)
+ if err != nil {
+ return 0
+ }
+ addr := ep.Addr()
if addr == nil {
return 0
}
@@ -53,18 +59,18 @@
return uint16(port)
}
-func newNeighborhoodServer(prefix, host string, eps []naming.Endpoint, loopback bool) (*neighborhood, error) {
- // Create the TXT contents with endpoints to announce. Also pick up a port number.
+func newNeighborhoodServer(prefix, host string, addresses []string, loopback bool) (*neighborhood, error) {
+ // Create the TXT contents with addresses to announce. Also pick up a port number.
var txt []string
var port uint16
- for _, ep := range eps {
- txt = append(txt, endpointPrefix+ep.String())
+ for _, addr := range addresses {
+ txt = append(txt, addressPrefix+addr)
if port == 0 {
- port = getPort(ep)
+ port = getPort(addr)
}
}
if txt == nil {
- return nil, errors.New("neighborhood passed no useful endpoint")
+ return nil, errors.New("neighborhood passed no useful addresses")
}
if port == 0 {
return nil, errors.New("neighborhood couldn't determine a port to use")
@@ -88,18 +94,18 @@
}
// NewLoopbackNeighborhoodServer creates a new instance of a neighborhood server on loopback interfaces for testing.
-func NewLoopbackNeighborhoodServer(prefix, host string, eps []naming.Endpoint) (*neighborhood, error) {
- return newNeighborhoodServer(prefix, host, eps, true)
+func NewLoopbackNeighborhoodServer(prefix, host string, addresses ...string) (*neighborhood, error) {
+ return newNeighborhoodServer(prefix, host, addresses, true)
}
// NewNeighborhoodServer creates a new instance of a neighborhood server.
-func NewNeighborhoodServer(prefix, host string, eps []naming.Endpoint) (*neighborhood, error) {
- return newNeighborhoodServer(prefix, host, eps, false)
+func NewNeighborhoodServer(prefix, host string, addresses ...string) (*neighborhood, error) {
+ return newNeighborhoodServer(prefix, host, addresses, false)
}
// Lookup implements ipc.Dispatcher.Lookup.
func (nh *neighborhood) Lookup(name string) (ipc.Invoker, security.Authorizer, error) {
- vlog.Infof("*********************LookupServer '%s'\n", name)
+ vlog.VI(1).Infof("*********************LookupServer '%s'\n", name)
elems := strings.Split(name, "/")[nh.nelems:]
if name == "" {
elems = nil
@@ -109,7 +115,13 @@
elems: elems,
nh: nh,
}
- return ipc.ReflectInvoker(mounttable.NewServerMountTable(ns)), nil, nil
+ return ipc.ReflectInvoker(mounttable.NewServerMountTable(ns)), nh, nil
+}
+
+func (nh *neighborhood) Authorize(context security.Context) error {
+ // TODO(rthellend): Figure out whether it's OK to accept all requests
+ // unconditionally.
+ return nil
}
// Stop performs cleanup.
@@ -122,30 +134,30 @@
var reply []mounttable.MountedServer
si := nh.mdns.ResolveInstance(instance, "veyron")
- // If we have any TXT records with endpoints, they take precedence.
+ // Look for any TXT records with addresses.
for _, rr := range si.TxtRRs {
- // Use a map to dedup any endpoints seen
- epmap := make(map[string]uint32)
+ // Use a map to dedup any addresses seen
+ addrMap := make(map[string]uint32)
for _, s := range rr.Txt {
- if !strings.HasPrefix(s, endpointPrefix) {
+ if !strings.HasPrefix(s, addressPrefix) {
continue
}
- epstring := s[len(endpointPrefix):]
- // Make sure its a legal endpoint string.
- if _, err := rt.R().NewEndpoint(epstring); err != nil {
- continue
- }
- epmap[epstring] = rr.Header().Ttl
+ addr := s[len(addressPrefix):]
+ addrMap[addr] = rr.Header().Ttl
}
- for epstring, ttl := range epmap {
- reply = append(reply, mounttable.MountedServer{naming.JoinAddressName(epstring, ""), ttl})
+ for addr, ttl := range addrMap {
+ reply = append(reply, mounttable.MountedServer{addr, ttl})
}
}
+
if reply != nil {
return reply
}
// If we didn't get any direct endpoints, make some up from the target and port.
+ // TODO(rthellend): Do we need the code below? If we haven't received
+ // any results at this point, it would seem to indicate a bug somewhere
+ // because NeighborhoodServer won't start without at least one address.
for _, rr := range si.SrvRRs {
ips, ttl := nh.mdns.ResolveAddress(rr.Target)
for _, ip := range ips {
diff --git a/services/mounttable/lib/neighborhood_test.go b/services/mounttable/lib/neighborhood_test.go
index 5528fb3..707241b 100644
--- a/services/mounttable/lib/neighborhood_test.go
+++ b/services/mounttable/lib/neighborhood_test.go
@@ -38,12 +38,16 @@
boom(t, "Failed to Listen mount table: %s", err)
}
estr := e.String()
-
+ addresses := []string{
+ naming.JoinAddressName(estr, ""),
+ naming.JoinAddressName(estr, "suffix1"),
+ naming.JoinAddressName(estr, "suffix2"),
+ }
// Add neighborhood server.
nhPrefix := "neighborhood"
- nhd, err := NewLoopbackNeighborhoodServer(nhPrefix, "joeblow", []naming.Endpoint{e})
+ nhd, err := NewLoopbackNeighborhoodServer(nhPrefix, "joeblow", addresses...)
if err != nil {
- t.Logf("Failed to create neighborhood server: %s\n", err)
+ boom(t, "Failed to create neighborhood server: %s\n", err)
}
defer nhd.Stop()
if err := server.Register(nhPrefix, nhd); err != nil {
@@ -90,14 +94,22 @@
if len(servers) == 0 {
boom(t, "resolveStep returns no severs")
}
-
+L2:
for _, s := range servers {
- address, suffix := naming.SplitAddressName(s.Server)
- if len(suffix) != 0 {
- boom(t, "Endpoint %s: unexpected suffix", s.Server)
+ for _, a := range addresses {
+ if a == s.Server {
+ continue L2
+ }
}
- if address != e.String() {
- boom(t, "Expected %s got %s", e, address)
+ boom(t, "Unexpected address from resolveStep result: %v", s.Server)
+ }
+L3:
+ for _, a := range addresses {
+ for _, s := range servers {
+ if a == s.Server {
+ continue L3
+ }
}
+ boom(t, "Missing address from resolveStep result: %v", a)
}
}
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index 5749146..b73dca2 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -1,10 +1,11 @@
-// mounttabled is a simple mount table daemon.
+// mounttabled is a mount table daemon.
package main
import (
"flag"
"fmt"
"os"
+ "time"
"veyron2"
"veyron2/naming"
@@ -20,20 +21,29 @@
mountName = flag.String("name", "", "Name to mount this mountable as. Empty means don't mount.")
// TODO(rthellend): Remove the address flag when the config manager is working.
address = flag.String("address", ":0", "Address to listen on. Default is to use a randomly assigned port")
- prefix = flag.String("prefix", "mt", "The prefix to register the server at.")
+ prefix = flag.String("prefix", "mt", "The prefix to register the mounttable at.")
aclFile = flag.String("acls", "", "ACL file. Default is to allow all access.")
+ nhName = flag.String("neighborhood_name", "", "If non-empty, publish in the local neighborhood under this name.")
)
-const usage = `%s is a simple mount table daemon.
+const usage = `%s is a mount table daemon.
Usage:
- %s [--name=<name>]
+ %s [--address=<local address>] [--name=<name>] [--neighborhood_name=<nh name>]
+
+ <local address> is the the local address to listen on. By default, it will
+ use a random port.
<name>, if provided, causes the mount table to mount itself under that name.
The name may be absolute for a remote mount table service (e.g., "/<remote mt
address>//some/suffix") or could be relative to this process' default mount
table (e.g., "some/suffix").
+
+ <nh name>, if provided, will enable sharing with the local neighborhood with
+ the provided name. The address of this mounttable will be published to the
+ neighboorhood and everything in the neighborhood will be visible on this
+ mounttable with the "nh" prefix.
`
func Usage() {
@@ -68,13 +78,37 @@
return
}
+ if len(*nhName) > 0 {
+ mtAddr := naming.JoinAddressName(endpoint.String(), *prefix)
+
+ nh, err := mounttable.NewNeighborhoodServer("", *nhName, mtAddr)
+ if err != nil {
+ vlog.Errorf("NewNeighborhoodServer failed: %v", err)
+ return
+ }
+ nhPrefix := "nh"
+ if err := server.Register(nhPrefix, nh); err != nil {
+ vlog.Errorf("server.Register failed to register neighborhood: %v", err)
+ return
+ }
+ nhAddr := naming.JoinAddressName(endpoint.String(), nhPrefix)
+ nhMount := naming.Join(mtAddr, nhPrefix)
+
+ ns := rt.R().Namespace()
+ forever := time.Duration(0)
+ if err = ns.Mount(rt.R().NewContext(), nhMount, nhAddr, forever); err != nil {
+ vlog.Errorf("ns.Mount failed to mount neighborhood: %v", err)
+ return
+ }
+ }
+
if name := *mountName; len(name) > 0 {
if err := server.Publish(name); err != nil {
vlog.Errorf("Publish(%v) failed: %v", name, err)
return
}
vlog.Infof("Mount table service at: %s (%s)",
- naming.JoinAddressName(name, *prefix),
+ naming.Join(name, *prefix),
naming.JoinAddressName(endpoint.String(), *prefix))
} else {