Merge "Added depcop configuration files to veyron packages"
diff --git a/examples/bank/bank/main.go b/examples/bank/bank/main.go
index 92228f6..1145486 100644
--- a/examples/bank/bank/main.go
+++ b/examples/bank/bank/main.go
@@ -95,7 +95,7 @@
}
// Make a NewClient and update the other one. Do not close the old client; we need its connection to the Mount Table.
- client, err = runtime.NewClient(veyron2.LocalID(derivedIdentity))
+ client, err = runtime.NewClient(veyron2.LocalID(newPublicID))
if err != nil {
log.Fatalf("failed to create new client: %s\n", err)
}
diff --git a/examples/boxes/android/src/boxesp2p/main.go b/examples/boxes/android/src/boxesp2p/main.go
index 8b0b724..a4748e3 100644
--- a/examples/boxes/android/src/boxesp2p/main.go
+++ b/examples/boxes/android/src/boxesp2p/main.go
@@ -398,7 +398,7 @@
// Initialize veyron runtime and bind to the signalling server used to rendezvous with
// another peer device. TODO(gauthamt): Switch to using the nameserver for signalling.
- gs.runtime = rt.Init(veyron2.LocalID(privateID))
+ gs.runtime = rt.Init(veyron2.RuntimeID(privateID))
if gs.signalling, err = boxes.BindBoxSignalling(naming.JoinAddressName("@2@tcp@162.222.181.93:8509@08a93d90836cd94d4dc1acbe40b9048d@1@1@@", "signalling")); err != nil {
panic(fmt.Errorf("failed to bind to signalling server:%v\n", err))
}
diff --git a/examples/boxes/boxes.vdl.go b/examples/boxes/boxes.vdl.go
index 40da80a..6dd142e 100644
--- a/examples/boxes/boxes.vdl.go
+++ b/examples/boxes/boxes.vdl.go
@@ -252,25 +252,43 @@
// Draw in the service interface DrawInterface.
type DrawInterfaceDrawStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
Send(item Box) 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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item Box, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -307,7 +325,7 @@
// Draw in the service interface DrawInterface.
type DrawInterfaceServiceDrawStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item Box) error
// Recv fills itemptr with the next item in the input stream, blocking until
diff --git a/examples/inspector/inspector.vdl.go b/examples/inspector/inspector.vdl.go
index 436f056..760c75f 100644
--- a/examples/inspector/inspector.vdl.go
+++ b/examples/inspector/inspector.vdl.go
@@ -49,14 +49,26 @@
type InspectorLsStream interface {
// Recv returns the next item in the input stream, blocking until
- // an item is available. Returns io.EOF to indicate graceful end of input.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item string, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish blocks until the server is done and returns the positional
+ // return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -85,7 +97,7 @@
// Ls in the service interface Inspector.
type InspectorServiceLsStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item string) error
}
@@ -103,14 +115,26 @@
type InspectorLsDetailsStream interface {
// Recv returns the next item in the input stream, blocking until
- // an item is available. Returns io.EOF to indicate graceful end of input.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item Details, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish blocks until the server is done and returns the positional
+ // return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -139,7 +163,7 @@
// LsDetails in the service interface Inspector.
type InspectorServiceLsDetailsStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item Details) error
}
diff --git a/examples/pipetobrowser/Makefile b/examples/pipetobrowser/Makefile
index 474caf5..1d3f652 100644
--- a/examples/pipetobrowser/Makefile
+++ b/examples/pipetobrowser/Makefile
@@ -1,12 +1,11 @@
-PATH:=$(PATH):node_modules/.bin
-PATH:=$(PATH):$(VEYRON_ROOT)/environment/cout/node/bin
-PATH:=$(PATH):$(VEYRON_ROOT)/veyron/go/bin
+PATH:=$(VEYRON_ROOT)/environment/cout/node/bin:$(PATH)
+PATH:=$(VEYRON_ROOT)/veyron/go/bin:$(PATH)
+PATH:=node_modules/.bin:../node_modules/.bin:$(PATH)
-VEYRON_PROXY_PORT=5164
-VEYRON_MOUNTTABLE_PORT=5167
-VEYRON_PROXY_ADDR=127.0.0.1:$(VEYRON_PROXY_PORT)
-VEYRON_WSPR_PORT=5165
-VEYRON_IDENTITY_PORT=5163
+VEYRON_PROXY_ADDR=proxy.envyor.com:8100
+VEYRON_WSPR_PORT=7776
+HTTP_PORT=8080
+NAMESPACE_ROOT=/proxy.envyor.com:8101
VEYRON_JS_API=$(VEYRON_ROOT)/veyron/javascript/api
VEYRON_BUILD_SCRIPT=$(VEYRON_ROOT)/veyron/scripts/build/go
VEYRON_IDENTITY_PATH=/tmp/p2b_identity
@@ -20,20 +19,21 @@
all: node_modules browser/third-party browser/third-party/veyron browser/build.js browser/index.html $(VEYRON_ROOT)/veyron/go/bin
# Build p2b cli binary
-$(VEYRON_ROOT)/veyron/go/bin: cli/main.go
- $(VEYRON_BUILD_SCRIPT) install veyron/examples/pipetobrowser/...
+$(VEYRON_ROOT)/veyron/go/bin: p2b/main.go
+ $(VEYRON_BUILD_SCRIPT) install veyron/...
# Install what we need from NPM, tools such as jspm, serve, etc...
node_modules: package.json
npm prune
npm install
touch node_modules
+ export
# Build and copies Veyron from local source
browser/third-party/veyron: $(VEYRON_JS_API)
- mkdir browser/third-party -p
+ mkdir -p browser/third-party
(cd $(VEYRON_JS_API) && ./vgrunt build)
- mkdir browser/third-party/veyron -p
+ mkdir -p browser/third-party/veyron
cp -rf $(VEYRON_JS_API)/dist/*.* browser/third-party/veyron
# Install JSPM and Bower packages as listed in browser/package.json from JSPM and browser/bower.json from bower
@@ -57,9 +57,18 @@
cd browser; \
vulcanize -o index.html app.html
-# Serve the files
+# Serve
start:
- serve browser/. --port 8080 --compress
+ identity --name=veyron_p2b_identity > $(VEYRON_IDENTITY_PATH)
+ export VEYRON_IDENTITY=$(VEYRON_IDENTITY_PATH) ; \
+ export NAMESPACE_ROOT=$(NAMESPACE_ROOT); \
+ wsprd --v=1 -alsologtostderr=true -vproxy=$(VEYRON_PROXY_ADDR) --port $(VEYRON_WSPR_PORT) & \
+ serve browser/. --port $(HTTP_PORT) --compress &
+
+# Stop serving
+stop:
+ pkill wsprd || true
+ lsof -i:$(HTTP_PORT) | grep node | awk '{printf "%s\n",$$2}' | xargs --no-run-if-empty kill
# Continuously watch for changes to .js, .html or .css files.
# Rebundle the appropriate file (build.js and/or index.html) when local files change
@@ -67,33 +76,10 @@
watch -n 1 make
# Clean all build artifacts
-clean:
+clean: stop
rm -rf browser/third-party
rm -rf node_modules
rm -f browser/index.html
rm -f browser/build.js
-# Deploys Veyron daemons
-daemons:
- @if [[ ! -e $(VEYRON_PROXY) ]]; then \
- echo "Veyron proxy could not be found in $(VEYRON_PROXY). Please build and install veyron2 and services first"; \
- exit 1; \
- fi
- identity --name=veyron_p2b_identity > $(VEYRON_IDENTITY_PATH)
- export VEYRON_IDENTITY=$(VEYRON_IDENTITY_PATH) ; \
- identityd --address=:$(VEYRON_IDENTITY_PORT) & \
- mounttabled --address=:$(VEYRON_MOUNTTABLE_PORT) & \
- export NAMESPACE_ROOT=/localhost:$(VEYRON_MOUNTTABLE_PORT) ; \
- proxyd -address=$(VEYRON_PROXY_ADDR) & \
- wsprd --v=1 -logtostderr=true -vproxy=$(VEYRON_PROXY_ADDR) --port $(VEYRON_WSPR_PORT) & \
- $(VEYRON_STORE) --address=:$(VEYRON_STORE_PORT) --name=global/$(USER)/store &
-
-# Kills the running daemons
-clean-daemons:
- kill `lsof -t -i:$(VEYRON_MOUNTTABLE_PORT)`; \
- kill `lsof -t -i:$(VEYRON_IDENTITY_PORT)`; \
- kill `lsof -t -i:$(VEYRON_WSPR_PORT)`; \
- kill `lsof -t -i:$(VEYRON_PROXY_PORT)`; \
- kill `lsof -t -i:$(VEYRON_STORE_PORT)`
-
-.PHONY: start clean daemons clean-daemons watch
\ No newline at end of file
+.PHONY: start stop clean watch
\ No newline at end of file
diff --git a/examples/pipetobrowser/README.md b/examples/pipetobrowser/README.md
index 2eedc9e..8a38312 100644
--- a/examples/pipetobrowser/README.md
+++ b/examples/pipetobrowser/README.md
@@ -24,11 +24,15 @@
``
To run
``
-make daemons #Runs the required Veyron daemons
make start #Starts a web server at 8080
``
and then navigate to http://localhost:8080
+To stop
+``
+make stop
+``
+
To clean
``
make clean
diff --git a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
index 419d70d..a284731 100644
--- a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
+++ b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
@@ -67,8 +67,11 @@
// Add a new tab and show a loading indicator for now,
// then replace the loading view with the actual viewer when ready
+ // close the stream when tab closes
var loadingView = new LoadingView();
- pipesViewInstance.addTab(tabKey, tabName, loadingView);
+ pipesViewInstance.addTab(tabKey, tabName, loadingView, () => {
+ stream.end();
+ });
// Add the redirect stream action
var icon = 'hardware:cast';
diff --git a/examples/pipetobrowser/browser/actions/navigate-help.js b/examples/pipetobrowser/browser/actions/navigate-help.js
index d2c0691..01f2117 100644
--- a/examples/pipetobrowser/browser/actions/navigate-help.js
+++ b/examples/pipetobrowser/browser/actions/navigate-help.js
@@ -12,7 +12,7 @@
import { HelpView } from 'views/help/view'
var log = new Logger('actions/navigate-help');
-const ACTION_NAME = 'help';
+var ACTION_NAME = 'help';
/*
* Registers the action
diff --git a/examples/pipetobrowser/browser/actions/redirect-pipe.js b/examples/pipetobrowser/browser/actions/redirect-pipe.js
index 28ce6d9..6bcb93b 100644
--- a/examples/pipetobrowser/browser/actions/redirect-pipe.js
+++ b/examples/pipetobrowser/browser/actions/redirect-pipe.js
@@ -61,7 +61,7 @@
getAllPublishedP2BNames().then((allNames) => {
// append current plugin name to the veyron names for better UX
dialog.existingNames = allNames.map((n) => {
- return n + (currentPluginName || '');
+ return n + '/' + currentPluginName;
});
}).catch((e) => {
log.debug('getAllPublishedP2BNames failed', e);
diff --git a/examples/pipetobrowser/browser/app.html b/examples/pipetobrowser/browser/app.html
index c592cb2..d4d7597 100644
--- a/examples/pipetobrowser/browser/app.html
+++ b/examples/pipetobrowser/browser/app.html
@@ -9,6 +9,15 @@
<title>Pipe To Browser - because life is too short to stare at unformatted stdout text, and is hard enough already not to have a spell-checker for stdin</title>
<script src="third-party/platform/platform.js"></script>
+ <!-- TODO(aghassemi) use CJS version of Veyron and provide an ES6 shim -->
+ <script src="third-party/veyron/veyron.js"></script>
+
+ <script src="third-party/traceur-runtime@0.0.49.js"></script>
+ <script src="third-party/system@0.6.js"></script>
+ <script src="config.js"></script>
+ <script src="shame.js"></script>
+ <script src="build.js"></script>
+
<link rel="import" href="views/page/component.html"/>
<style type="text/css">
@@ -19,13 +28,7 @@
</style>
</head>
<body>
- <!-- TODO(aghassemi) use CJS version of Veyron and provide an ES6 shim -->
- <script src="third-party/veyron/veyron.js"></script>
- <script src="third-party/traceur-runtime@0.0.49.js"></script>
- <script src="third-party/system@0.6.js"></script>
- <script src="config.js"></script>
- <script src="build.js"></script>
<script>
window.addEventListener('polymer-ready', function(e) {
diff --git a/examples/pipetobrowser/browser/bower.json b/examples/pipetobrowser/browser/bower.json
index 6ebd373..0217621 100644
--- a/examples/pipetobrowser/browser/bower.json
+++ b/examples/pipetobrowser/browser/bower.json
@@ -2,8 +2,8 @@
"name": "pipe-to-browser",
"version": "0.0.1",
"dependencies": {
- "polymer": "Polymer/polymer#~0.3.2",
- "core-elements": "Polymer/core-elements#~0.3.2",
- "paper-elements": "Polymer/paper-elements#~0.3.2"
+ "polymer": "Polymer/polymer#~0.3.4",
+ "core-elements": "Polymer/core-elements#~0.3.4",
+ "paper-elements": "Polymer/paper-elements#~0.3.4"
}
}
diff --git a/examples/pipetobrowser/browser/config.js b/examples/pipetobrowser/browser/config.js
index 2e7b142..4206c8a 100644
--- a/examples/pipetobrowser/browser/config.js
+++ b/examples/pipetobrowser/browser/config.js
@@ -6,6 +6,8 @@
"view": "libs/mvc/view.js",
"logger": "libs/logs/logger.js",
"stream-helpers": "libs/utils/stream-helpers.js",
+ "web-component-loader": "libs/utils/web-component-loader.js",
+ "formatting": "libs/utils/formatting.js",
"npm:*": "third-party/npm/*.js",
"github:*": "third-party/github/*.js"
}
@@ -13,41 +15,43 @@
System.config({
"map": {
+ "npm:humanize": "npm:humanize@^0.0.9",
"npm:event-stream": "npm:event-stream@^3.1.5",
"nodelibs": "github:jspm/nodelibs@master",
"npm:event-stream@3.1.5": {
- "through": "npm:through@^2.3.1",
- "pause-stream": "npm:pause-stream@0.0.11",
"from": "npm:from@0",
- "stream-combiner": "npm:stream-combiner@^0.0.4",
"map-stream": "npm:map-stream@0.1",
+ "pause-stream": "npm:pause-stream@0.0.11",
"duplexer": "npm:duplexer@^0.1.1",
- "split": "npm:split@0.2"
+ "through": "npm:through@^2.3.1",
+ "split": "npm:split@0.2",
+ "stream-combiner": "npm:stream-combiner@^0.0.4"
},
+ "npm:humanize@0.0.9": {},
+ "npm:from@0.1.3": {},
"npm:stream-combiner@0.0.4": {
"duplexer": "npm:duplexer@^0.1.1"
},
+ "npm:duplexer@0.1.1": {},
+ "npm:map-stream@0.1.0": {},
"npm:pause-stream@0.0.11": {
"through": "npm:through@2.3"
},
- "npm:from@0.1.3": {},
- "npm:through@2.3.4": {},
- "npm:duplexer@0.1.1": {},
"npm:split@0.2.10": {
"through": "npm:through@2"
},
- "npm:map-stream@0.1.0": {},
+ "npm:through@2.3.4": {},
"github:jspm/nodelibs@0.0.2": {
- "base64-js": "npm:base64-js@^0.0.4",
"ieee754": "npm:ieee754@^1.1.1",
- "inherits": "npm:inherits@^2.0.1",
+ "base64-js": "npm:base64-js@^0.0.4",
"Base64": "npm:Base64@0.2",
+ "inherits": "npm:inherits@^2.0.1",
"json": "github:systemjs/plugin-json@master"
},
"npm:base64-js@0.0.4": {},
+ "npm:ieee754@1.1.3": {},
"npm:Base64@0.2.1": {},
"npm:inherits@2.0.1": {},
- "npm:ieee754@1.1.3": {},
"github:jspm/nodelibs@master": {
"Base64": "npm:Base64@0.2",
"base64-js": "npm:base64-js@^0.0.4",
@@ -60,22 +64,23 @@
System.config({
"versions": {
+ "npm:humanize": "0.0.9",
"npm:event-stream": "3.1.5",
- "npm:through": "2.3.4",
- "npm:pause-stream": "0.0.11",
"npm:from": "0.1.3",
- "npm:stream-combiner": "0.0.4",
"npm:map-stream": "0.1.0",
+ "npm:pause-stream": "0.0.11",
"npm:duplexer": "0.1.1",
+ "npm:through": "2.3.4",
"npm:split": "0.2.10",
+ "npm:stream-combiner": "0.0.4",
"github:jspm/nodelibs": [
"master",
"0.0.2"
],
- "npm:base64-js": "0.0.4",
"npm:ieee754": "1.1.3",
- "npm:inherits": "2.0.1",
+ "npm:base64-js": "0.0.4",
"npm:Base64": "0.2.1",
+ "npm:inherits": "2.0.1",
"github:systemjs/plugin-json": "master"
}
});
diff --git a/examples/pipetobrowser/browser/config/config.js b/examples/pipetobrowser/browser/config/config.js
index de4cd2e..ce740a4 100644
--- a/examples/pipetobrowser/browser/config/config.js
+++ b/examples/pipetobrowser/browser/config/config.js
@@ -6,10 +6,9 @@
export var config = {
veyron: {
- identityServer: 'http://localhost:5163/random/',
- proxy: 'http://localhost:5165',
+ proxy: 'http://localhost:7776',
logLevel: veyronLogLevels.INFO
},
- namespaceRoot: '/localhost:5167',
+ namespaceRoot: '/proxy.envyor.com:8101',
publishNamePrefix: 'google/p2b'
}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
index cb2f8c9..208ff49 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
@@ -38,9 +38,9 @@
}
if (this.gridState.sort.ascending) {
- return this.data.label + ' \u21A7'; // up wedge unicode character
+ return this.data.label + ' \u21A5'; // up wedge unicode character
} else {
- return this.data.label + ' \u21A5'; // down wedge unicode character
+ return this.data.label + ' \u21A7'; // down wedge unicode character
}
}
});
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
index 87cdff3..b309a31 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
@@ -26,6 +26,7 @@
.more-icon {
fill: #0a7e07;
+ color: #0a7e07;
}
paper-dialog {
@@ -33,11 +34,6 @@
width: 80vw;
}
-/* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
-paper-dialog /deep/ #shadow {
- z-index: -1;
-}
-
.more-dialog-content .heading {
font-size: 1.0em;
padding-top: 0.2em;
@@ -85,6 +81,10 @@
display: initial;
}
+.more-dialog-content [gridOnly] {
+ display:none;
+}
+
.paginator {
display: inline-block;
border: solid 1px rgba(0, 0, 0, 0.05);
@@ -97,4 +97,4 @@
.paginator paper-icon-button {
vertical-align: middle;
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
index a67f7dd..4f555ab 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -157,25 +157,25 @@
/*
* Number if items displayed in each page.
- * Defaults to 30
+ * Defaults to 20
* @type {integer}
*/
- pageSize: 30,
+ pageSize: 20,
showMoreInfo: function(e) {
var item = e.target.templateInstance.model.item;
this.selectedItems = [item];
- this.$.dialog.opened = true;
+ this.$.dialog.toggle();
},
ready: function() {
// private property fields
this.columns = [];
- this.shouldRefetchData = true;
this.pageNumber = 1;
this.dataSource = null;
this.cachedDataSourceResult = [];
+ this.dataSourceResult = [];
this.gridState = {
sort: {
key: '',
@@ -308,7 +308,7 @@
if (goBackToPageOne) {
self.pageNumber = 1;
}
- self.shouldRefetchData = true;
+ self.updateDataSource();
});
},
@@ -333,7 +333,7 @@
col.columnData.flex = col.columnData.origFlex;
}
- if (tableWidth >= minWidth) {
+ if (tableWidth === 0 || tableWidth >= minWidth) {
return;
}
@@ -391,9 +391,9 @@
* Only fetches data if scheduled to do so
* @private
*/
- get dataSourceResult() {
- if (!this.shouldRefetchData || !this.dataSource) {
- return this.cachedDataSourceResult;
+ updateDataSource: function() {
+ if (!this.dataSource) {
+ return;
}
// fetch the data
@@ -417,9 +417,7 @@
var endIndex = startIndex + this.pageSize;
this.cachedDataSourceResult = this.cachedDataSourceResult.slice(startIndex, endIndex);
- this.shouldRefetchData = false;
-
- return this.cachedDataSourceResult;
+ this.dataSourceResult = this.cachedDataSourceResult;
},
/*
diff --git a/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js b/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
index d12c998..90593e1 100644
--- a/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
+++ b/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
@@ -3,9 +3,6 @@
var Transform = Stream.Transform;
var Buffer = buffer.Buffer;
-// TODO(aghassemi) doesn't look like ES6 and CommonJS modules can use the same
-// syntax to be referenced, but research more, maybe something can be done at
-// built time.
/*
* Adapts a stream of byte arrays in object mode to a regular stream of Buffer
diff --git a/examples/pipetobrowser/browser/libs/utils/formatting.js b/examples/pipetobrowser/browser/libs/utils/formatting.js
new file mode 100644
index 0000000..26f90e6
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/formatting.js
@@ -0,0 +1,23 @@
+import { default as humanize } from 'npm:humanize'
+
+export function formatDate(d) {
+ if(d === undefined || d == null) { return; }
+ var naturalDay = humanize.naturalDay(d.getTime() / 1000);
+ var naturalTime = humanize.date('g:i a', d);
+ return naturalDay + ' at ' + naturalTime;
+}
+
+export function formatRelativeTime(d) {
+ if(d === undefined || d == null) { return; }
+ return humanize.relativeTime(d.getTime() / 1000);
+}
+
+export function formatInteger(n) {
+ if(n === undefined || n == null) { return; }
+ return humanize.numberFormat(n, 0);
+}
+
+export function formatBytes(b) {
+ if(b === undefined || b == null) { return; }
+ return humanize.filesize(b);
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/utils/stream-copy.js b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
index 4b7e386..c940213 100644
--- a/examples/pipetobrowser/browser/libs/utils/stream-copy.js
+++ b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
@@ -17,6 +17,13 @@
// TODO(aghassemi) make this a FIFO buffer with reasonable max-size
this.buffer = [];
this.copies = [];
+ var self = this;
+ this.on('end', () => {
+ self.ended = true;
+ for (var i=0; i < self.copies.length; i++) {
+ self.copies[i].end();
+ }
+ });
}
_transform(chunk, encoding, cb) {
@@ -42,7 +49,12 @@
copy.push(this.buffer[i]);
}
}
- this.copies.push(copy);
+ if (this.ended) {
+ copy.push(null);
+ } else {
+ this.copies.push(copy);
+ }
+
return copy;
}
}
diff --git a/examples/pipetobrowser/browser/libs/utils/time.js b/examples/pipetobrowser/browser/libs/utils/time.js
deleted file mode 100644
index be9b42d..0000000
--- a/examples/pipetobrowser/browser/libs/utils/time.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Given a time duration in seconds, formats it as h' hours, m' minutes, s' seconds
- * in EN-US.
- * @param {integer} durationInSeconds Time period in seconds
- * @return {string} EN-US formatted time period.
- */
-export function formatDuration(durationInSeconds) {
- var hours = Math.floor(durationInSeconds/3600);
- var minutes = Math.floor((durationInSeconds - (hours*3600))/60);
- var seconds = durationInSeconds - (hours*3600) - (minutes*60);
-
- return _pluralize('hour', hours, true) +
- _pluralize('minute', minutes, true) +
- _pluralize('second', seconds, false);
-}
-
-function _pluralize(name, value, returnEmptyIfZero) {
- if(value == 0 && returnEmptyIfZero) {
- return '';
- }
- if(value != 1) {
- name = name + 's';
- }
- return value + ' ' + name + ' ';
-}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/utils/web-component-loader.js b/examples/pipetobrowser/browser/libs/utils/web-component-loader.js
new file mode 100644
index 0000000..bb37e0c
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/web-component-loader.js
@@ -0,0 +1,11 @@
+export function importComponent(path) {
+ return new Promise((resolve, reject) => {
+ var link = document.createElement('link');
+ link.setAttribute('rel', 'import');
+ link.setAttribute('href', path);
+ link.onload = function() {
+ resolve();
+ };
+ document.body.appendChild(link);
+ });
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/package.json b/examples/pipetobrowser/browser/package.json
index 773f16f..93fe433 100644
--- a/examples/pipetobrowser/browser/package.json
+++ b/examples/pipetobrowser/browser/package.json
@@ -6,6 +6,7 @@
},
"dependencies": {
"npm:event-stream": "^3.1.5",
+ "npm:humanize": "^0.0.9",
"nodelibs": "master"
}
}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
index 2091d34..d1ef530 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
@@ -15,3 +15,12 @@
color: #ffffff;
}
+
+.auto-scroll {
+ position: fixed;
+ right: 40px;
+ bottom: 0;
+ opacity: 0.8;
+ padding: 0.8em;
+ background-color: #ffeb3b;
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
index d816aed..5505f9b 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
@@ -12,8 +12,11 @@
</template>
<script>
Polymer('p2b-plugin-console', {
- autoScroll: true,
- textBuffer: [],
+ ready: function() {
+ this.textBuffer = [];
+ this.autoScroll = true;
+ },
+
attached: function() {
this.renderLoop();
},
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
index 9b266fe..99617a1 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
@@ -7,7 +7,8 @@
}
::shadow /deep/ .state-icon.conflicted {
- fill: #e51c23;
+ fill: #bf360c;
+ -webkit-animation: blink 0.6s infinite alternate;
}
::shadow /deep/ .state-icon.untracked {
@@ -15,7 +16,7 @@
}
::shadow /deep/ .state-icon.ignored {
- fill: #bf360c;
+ fill: #689f38;
}
::shadow /deep/ .action-icon {
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 7ac8c63..11583c5 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -91,10 +91,10 @@
iconName = 'warning';
break;
case 'conflicted':
- iconName = 'error';
+ iconName = 'block';
break;
case 'untracked':
- iconName = 'report';
+ iconName = 'error';
break;
case 'ignored':
iconName = 'visibility-off';
@@ -122,7 +122,7 @@
iconName = 'translate';
break;
case 'renamed':
- iconName = 'sync';
+ iconName = 'swap-horiz';
break;
case 'copied':
iconName = 'content-copy';
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
index 59f4f28..3a70a5c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -12,7 +12,7 @@
<link rel="stylesheet" href="../../../libs/css/common-style.css">
<link rel="stylesheet" href="component.css">
- <p2b-grid id="grid" defaultSortKey="date" defaultSortAscending dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
+ <p2b-grid id="grid" defaultSortKey="date" dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
<!-- Search -->
<p2b-grid-search label="Search Logs"></p2b-grid-search>
@@ -48,8 +48,12 @@
<p2b-grid-column label="Message" key="message" primary flex="8" minFlex="5" priority="1" >
<template><div class="message-text">{{ item.message }}</div></template>
</p2b-grid-column>
- <p2b-grid-column label="Date" key="date" sortable flex="6" minFlex="3" priority="3">
- <template><span class="smaller-text">{{ item.date }}</span></template>
+ <p2b-grid-column label="Date" key="date" sortable flex="4" minFlex="3" priority="3">
+ <template>
+ <abbr gridOnly title="{{item.date}}">{{ item.formattedDate }}</abbr>
+ <span moreInfoOnly>{{item.date}}</span>
+ </template>
+
</p2b-grid-column>
<p2b-grid-column label="Threadid" key="threadid" sortable flex="0" priority="5">
<template>{{ item.threadId }}</template>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index 76d52a2..d011cfd 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -9,6 +9,7 @@
import { View } from 'view';
import { PipeViewer } from 'pipe-viewer';
import { streamUtil } from 'stream-helpers';
+import { formatDate } from 'formatting';
import { Logger } from 'logger'
import { vLogDataSource } from './data-source';
@@ -22,18 +23,27 @@
play(stream) {
stream.setEncoding('utf8');
+ var logView = document.createElement('p2b-plugin-vlog');
+ var newData = true;
+ var refreshGrid = function() {
+ requestAnimationFrame(() => {
+ if( newData ) {
+ logView.refreshGrid();
+ newData = false;
+ }
+ refreshGrid();
+ });
+ };
+ refreshGrid();
+
// split by new line
stream = stream.pipe(streamUtil.split(/\r?\n/));
- var logView = document.createElement('p2b-plugin-vlog');
// create a new data source from the stream and set it.
logView.dataSource = new vLogDataSource(
stream,
function onNewItem(item) {
- // also refresh the grid when new data comes in.
- // grid component batches requests and refreshes UI on the next animation frame.
- logView.refreshGrid();
-
+ newData = true;
// add additional, UI related properties to the item
addAdditionalUIProperties(item);
},
@@ -51,6 +61,7 @@
*/
function addAdditionalUIProperties(item) {
addIconProperty(item);
+ addFormattedDate(item);
}
/*
@@ -78,4 +89,12 @@
item.icon = iconName;
}
+/*
+ * Adds a human friendly date field
+ * @private
+ */
+function addFormattedDate(item) {
+ item.formattedDate = formatDate(item.date);
+}
+
export default vLogPipeViewer;
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/manager.js b/examples/pipetobrowser/browser/pipe-viewers/manager.js
index d135741..4c7dea0 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/manager.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/manager.js
@@ -82,7 +82,9 @@
*/
function getPath(name) {
if(isAbsoulteUrl(name)) {
- return name;
+ var encodedName = encodeURIComponent(name);
+ System.paths[encodedName] = name;
+ return encodedName;
} else {
return 'pipe-viewers/builtin/' + name + '/plugin';
}
diff --git a/examples/pipetobrowser/browser/services/pipe-to-browser-server.js b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
index cf04487..ac4e0f4 100644
--- a/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
+++ b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
@@ -69,19 +69,32 @@
var stream = streamCopier.pipe(bufferStream).pipe(streamByteCounter);
stream.copier = streamCopier;
- bufferStream.on('end', () => {
+ streamByteCounter.on('end', () => {
log.debug('end of stream');
// send total number of bytes received for this call as final result
- resolve(numBytesForThisCall);
+ resolve();
});
- bufferStream.on('error', (e) => {
+ stream.on('error', (e) => {
log.debug('stream error', e);
- reject(e);
+ // TODO(aghassemi) envyor issue #50
+ // we want to reject but because of #50 we can't
+ // reject('Browser P2B threw an exception. Please see browser console for details.');
+ // reject(e);
+ resolve();
});
state.numPipes++;
- pipeRequestHandler($suffix, stream);
+ try {
+ pipeRequestHandler($suffix, stream);
+ } catch(e) {
+ // TODO(aghassemi) envyor issue #50
+ // we want to reject but because of #50 we can't
+ // reject('Browser P2B threw an exception. Please see browser console for details.');
+ log.debug('pipeRequestHandler error', e);
+ resolve();
+ }
+
});
}
};
diff --git a/examples/pipetobrowser/browser/shame.js b/examples/pipetobrowser/browser/shame.js
new file mode 100644
index 0000000..10cb74f
--- /dev/null
+++ b/examples/pipetobrowser/browser/shame.js
@@ -0,0 +1,5 @@
+//TODO(aghassemi) Ugly random identity hack until we have proper identity
+var id= '_4EEGgFCAP-DNBoBQwEudmV5cm9uL3J1bnRpbWVzL2dvb2dsZS9zZWN1cml0eS5jaGFpblByaXZhdGVJRAD_hUIYAQIBRAEIUHVibGljSUQAAQQBBlNlY3JldAABJHZleXJvbjIvc2VjdXJpdHkvd2lyZS5DaGFpblByaXZhdGVJRAD_hzoYAQEBRQEMQ2VydGlmaWNhdGVzAAEjdmV5cm9uMi9zZWN1cml0eS93aXJlLkNoYWluUHVibGljSUQA_4kEEgFGAP-LWBgBBAEDAQROYW1lAAFHAQlQdWJsaWNLZXkAAUgBB0NhdmVhdHMAAUkBCVNpZ25hdHVyZQABIXZleXJvbjIvc2VjdXJpdHkvd2lyZS5DZXJ0aWZpY2F0ZQD_jTYYAQIBSgEFQ3VydmUAAQQBAlhZAAEfdmV5cm9uMi9zZWN1cml0eS93aXJlLlB1YmxpY0tleQD_kyQQATIBHnZleXJvbjIvc2VjdXJpdHkvd2lyZS5LZXlDdXJ2ZQD_jwQSAUsA_5U4GAECAUwBB1NlcnZpY2UAAQQBBUJ5dGVzAAEcdmV5cm9uMi9zZWN1cml0eS93aXJlLkNhdmVhdAD_lycQAQMBIXZleXJvbjIvc2VjdXJpdHkuUHJpbmNpcGFsUGF0dGVybgD_kTEYAQIBBAEBUgABBAEBUwABH3ZleXJvbjIvc2VjdXJpdHkvd2lyZS5TaWduYXR1cmUA_4L-Ae4BAwEBAgETdmV5cm9uX3AyYl9pZGVudGl0eQECQQSX1W2szGUlXbia28KqD7tzpVIHappQOKvixbijDauOdV6YrygMAmky5vLetcPzmf2Kz4QQfjf-_XpaiH2vJ3SFAAIBIEQgGWkP4DbWtZkKobftfkqE7-hPhldI1E7WM-9NHqwEASB_4e5WDr35ffYa16HkDm2pxoICFc2HemwTXgl-n5u1iQAAAQpyYW5kb206NTQwAQJBBPDpM7apzfyfgANbP3HKIo_vCTc2Jq4KbmHE8_OGjF7hkS5Do4nh6_Q-SBB1nnqSkpslgBU1kGXUCeh0P0Jza3kAAQEBASoB_4P_gQQaAUIA_4NAGAECAUMBCUlzc3VlVGltZQABQwEKRXhwaXJ5VGltZQABHXZleXJvbi9zZWN1cml0eS9jYXZlYXQuRXhwaXJ5AP-FDxABBAEJdGltZS5UaW1lAP-CJAEBDwEAAAAOy1ic8jYxcFX-XAEPAQAAAA7LWKsCNjFwVf5cAAABASBOr9ic8ql0ZSmb3HA1Z3yEJHmt62SqXHjrDT99E8buFwEgFjrqh7PJdSs8hQxl3eKyYhGfvCcULfzX0mrWBp-k_v0AAAABIML_mzA_ofWsFLMlvFukoE6vkZBJh3b7rOIxFK0HENPcAA==';
+Veyron.prototype._getIdentityPromise = function() {
+ return Promise.resolve(id);
+};
diff --git a/examples/pipetobrowser/browser/views/help/component.css b/examples/pipetobrowser/browser/views/help/component.css
index faf1990..1e2e557 100644
--- a/examples/pipetobrowser/browser/views/help/component.css
+++ b/examples/pipetobrowser/browser/views/help/component.css
@@ -29,4 +29,5 @@
a {
color: #5677fc;
text-decoration: none;
+ cursor: pointer;
}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/help/component.html b/examples/pipetobrowser/browser/views/help/component.html
index 46e1d2c..ba5112c 100644
--- a/examples/pipetobrowser/browser/views/help/component.html
+++ b/examples/pipetobrowser/browser/views/help/component.html
@@ -1,4 +1,4 @@
-<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="../../third-party/polymer/polymer.html">
<polymer-element name="p2b-help">
<template>
@@ -40,10 +40,13 @@
<pre class="code">cat /dev/urandom | p2b {{publishedName}}/dev/null</pre>
<h3>Remote Viewers</h3>
- <p>In addition to built-in viewers, ad-hoc remote viewers can be hosted anywhere and used with P2B. Remote viewers are referenced by their Url without the .js extension at the end og the plug-in JavaScript file</p>
- <pre class="code">echo "Hello World" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld</pre>
+ <p>In addition to built-in viewers, ad-hoc remote viewers can be hosted anywhere and used with P2B. Remote viewers are referenced by the Url of the plug-in JavaScript file</p>
+ <pre class="code">echo "Hello World" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld.js</pre>
<p>Writing remote viewers is not different than writing built-in ones and basic plug-ins are pretty straight forward to write.</p>
<p>At high level, plug-ins are expected to implement a <span class="mono">PipeViewer</span> interface which has a <span class="mono">play(stream)</span> method. A <span class="mono">view</span> (which is a wrapper for a DOM element) is expected to be returned from <span class="mono">play(stream)</span>. You can look at the hello world remote plug-in <a href="http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld.js" target="_blank">code on Google drive</a> to get started on writing new remote plug-ins</p>
+ <p>It is also possible to write the UI layer of your plug-in in HTML and CSS as a Web Component to avoid mixing logic and layout/styling in a single file.</p>
+ <p>Grumpy cat meme plug-in takes that approach. You can look at the <a href="http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.js" target="_blank">JavaScript</a> and <a onClick="window.open('view-source:' + 'http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.html');">HTML Web Component</a> source files.</p>
+ <pre class="code">echo "I take stuff from stdin, and send them to /dev/null" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.js</pre>
</template>
<script>
Polymer('p2b-help', {
diff --git a/examples/pipetobrowser/browser/views/loading/component.html b/examples/pipetobrowser/browser/views/loading/component.html
index 77e5a29..b6db519 100644
--- a/examples/pipetobrowser/browser/views/loading/component.html
+++ b/examples/pipetobrowser/browser/views/loading/component.html
@@ -3,7 +3,7 @@
<polymer-element name="p2b-loading">
<template>
<link rel="stylesheet" href="component.css">
- <img class="spinner" src="/libs/ui-components/common/spinner.gif" alt="Loading"/>
+ <img class="spinner" src="../../libs/ui-components/common/spinner.gif" alt="Loading"/>
</template>
<script>
Polymer('p2b-loading', {
diff --git a/examples/pipetobrowser/browser/views/page/component.css b/examples/pipetobrowser/browser/views/page/component.css
index 787f3bb..793659b 100644
--- a/examples/pipetobrowser/browser/views/page/component.css
+++ b/examples/pipetobrowser/browser/views/page/component.css
@@ -11,12 +11,10 @@
[sidebar] {
padding: 0.5em;
- fill: #9e9e9e;
}
[sidebar] .core-selected {
color: #00bcd4;
- fill: #212121;
}
[main] {
diff --git a/examples/pipetobrowser/browser/views/page/component.html b/examples/pipetobrowser/browser/views/page/component.html
index 0993f2a..4e5b72d 100644
--- a/examples/pipetobrowser/browser/views/page/component.html
+++ b/examples/pipetobrowser/browser/views/page/component.html
@@ -15,6 +15,7 @@
<link rel="import" href="../../views/publish/component.html"/>
<link rel="import" href="../../views/status/component.html"/>
<link rel="import" href="../../views/error/component.html"/>
+<link rel="import" href="../../views/help/component.html"/>
<link rel="import" href="../../views/loading/component.html"/>
<link rel="import" href="../../views/pipes/component.html"/>
<link rel="import" href="../../views/redirect-pipe-dialog/component.html"/>
@@ -46,7 +47,7 @@
<core-header-panel main>
<core-toolbar >
<paper-icon-button icon="menu" on-click="{{ toggleDrawer }}"></paper-icon-button>
- <h2>{{ title }}</h2>
+ <h2>{{ pageTitle }}</h2>
</core-toolbar>
<core-selector id="subPagesSelector" valueattr="key" selected="{{ selectedSubPageKey }}">
@@ -65,7 +66,7 @@
* Title of the page
* @type {string}
*/
- title: '',
+ pageTitle: '',
/*
* SubPageItem represents top level sub pages that have a sidebar navigation link
diff --git a/examples/pipetobrowser/browser/views/page/view.js b/examples/pipetobrowser/browser/views/page/view.js
index b2e7aa0..e946181 100644
--- a/examples/pipetobrowser/browser/views/page/view.js
+++ b/examples/pipetobrowser/browser/views/page/view.js
@@ -43,7 +43,7 @@
* @type {string}
*/
set title(title) {
- this.element.title = title;
+ this.element.pageTitle = title;
}
}
diff --git a/examples/pipetobrowser/browser/views/pipes/component.html b/examples/pipetobrowser/browser/views/pipes/component.html
index 9558d16..a2a2286 100644
--- a/examples/pipetobrowser/browser/views/pipes/component.html
+++ b/examples/pipetobrowser/browser/views/pipes/component.html
@@ -47,13 +47,8 @@
*/
selectionHistoryStack: [],
- /*
- * Number of open tabs
- * @type {integer}
- * @private
- */
- get numTabs() {
- return Object.keys(this.pipeTabs).length;
+ ready: function() {
+ this.numTabs = 0
},
/*
@@ -61,8 +56,9 @@
* @param {string} key Key of the tab to add
* @param {string} name Name of the tab to add
* @param {DOMElement} el Content of the tab
+ * @param {function} onClose Optional onClose callback.
*/
- addTab: function(key, name, el) {
+ addTab: function(key, name, el, onClose) {
var self = this;
// Create a tab thumb
@@ -72,9 +68,12 @@
// Create a tab toolbar and assign the close handler
var tabToolbar = document.createElement('p2b-pipes-tab-toolbar');
- tabToolbar.title = name;
+ tabToolbar.toolbarTitle = name;
tabToolbar.addEventListener('close-action', function() {
self.removeTab(key);
+ if (onClose) {
+ onClose();
+ }
});
tabToolbar.addEventListener('fullscreen-action', function() {
var tabContent = self.pipeTabs[key].tabContent;
@@ -88,7 +87,6 @@
tabContent.appendChild(el);
this.$.tabPages.appendChild(tabContent);
- this.$.tabs.appendChild(tab);
// Add the tab to our list.
this.pipeTabs[key] = {
@@ -98,7 +96,12 @@
tabToolbar: tabToolbar
};
+ this.numTabs++;
+
this.selectedTabKey = key;
+ requestAnimationFrame(function() {
+ self.$.tabs.appendChild(tab);
+ });
},
/*
@@ -108,6 +111,9 @@
* @param onClick {function} event handler for the action
*/
addToolbarAction: function(tabKey, icon, onClick) {
+ if (!this.pipeTabs[tabKey]) {
+ return;
+ }
var toolbar = this.pipeTabs[tabKey].tabToolbar;
toolbar.add(icon, onClick);
},
@@ -117,6 +123,9 @@
* @param {string} key Key of the tab to remove
*/
removeTab: function(key) {
+ if (!this.pipeTabs[key]) {
+ return;
+ }
// Remove tab thumb and content
var tab = this.pipeTabs[key].tab;
tab.remove();
@@ -125,6 +134,7 @@
// Delete tab from the map
delete this.pipeTabs[key];
+ this.numTabs--;
// Select an existing tab from previous selection history
var toSelect = this.selectionHistoryStack.pop();
@@ -144,11 +154,14 @@
* @param {DOMElement} el New content of the tab
*/
replaceTabContent: function(key, newName, newEl) {
+ if (!this.pipeTabs[key]) {
+ return;
+ }
var tabContent = this.pipeTabs[key].tabContent;
tabContent.replaceTabContent(newEl);
if (newName) {
this.pipeTabs[key].tab.textContent = newName;
- this.pipeTabs[key].tabToolbar.title = newName;
+ this.pipeTabs[key].tabToolbar.toolbarTitle = newName;
}
},
diff --git a/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html b/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
index 25799ef..3514b67 100644
--- a/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
+++ b/examples/pipetobrowser/browser/views/pipes/tab-toolbar/component.html
@@ -6,7 +6,7 @@
<link rel="stylesheet" href="component.css">
<core-toolbar>
<span flex>
- {{ title }}
+ {{ toolbarTitle }}
</span>
<span id="customActions"></span>
<paper-icon-button id="fullscreenIcon" icon="fullscreen" on-click="{{ fireFullscreenAction }}"></paper-icon-button>
@@ -20,7 +20,7 @@
* Title of the toolbar
* @type {string}
*/
- title: '',
+ toolbarTitle: '',
/*
* Event that's fired when close action of the toolbar is triggered
diff --git a/examples/pipetobrowser/browser/views/pipes/view.js b/examples/pipetobrowser/browser/views/pipes/view.js
index 8c9e2b4..78fcd20 100644
--- a/examples/pipetobrowser/browser/views/pipes/view.js
+++ b/examples/pipetobrowser/browser/views/pipes/view.js
@@ -18,9 +18,10 @@
* @param {string} name A short name for the tab that will be displayed as
* the tab title
* @param {View} view View to show inside the tab.
+ * @param {function} onClose Optional onClose callback.
*/
- addTab(key, name, view) {
- this.element.addTab(key, name, view.element);
+ addTab(key, name, view, onClose) {
+ this.element.addTab(key, name, view.element, onClose);
}
/*
diff --git a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
index a58b136..b1bdca0 100644
--- a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
+++ b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
@@ -11,11 +11,6 @@
width: 80vw;
}
-/* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
-paper-dialog /deep/ #shadow {
- z-index: -1 !important;
-}
-
paper-button[affirmative] {
color: #4285f4;
}
diff --git a/examples/pipetobrowser/browser/views/status/component.html b/examples/pipetobrowser/browser/views/status/component.html
index ca511a2..14143c1 100644
--- a/examples/pipetobrowser/browser/views/status/component.html
+++ b/examples/pipetobrowser/browser/views/status/component.html
@@ -1,4 +1,5 @@
<link rel="import" href="../../third-party/polymer/polymer.html">
+<link rel="import" href="../../third-party/paper-button/paper-button.html">
<polymer-element name="p2b-status" attributes="status">
@@ -6,118 +7,96 @@
<link rel="stylesheet" href="../common/common.css">
<link rel="stylesheet" href="component.css">
<h3>Status</h3>
- <p>{{ statusText }}</p>
- <div class="{{ {hidden : !serviceState.published} | tokenList}}">
+ <p>{{ serviceState | formatServiceState }}</p>
+ <div class="{{ {hidden : !serviceState.published} | tokenList }}">
<h3>Name</h3>
<p>{{ serviceState.fullServiceName }}</p>
<h3>Published on</h3>
- <p>{{ publishDate }}</p>
+ <p>{{ serviceState.date | formatDate }}</p>
- <h3>Uptime</h3>
- <p>{{ uptime }}</p>
+ <h3>Running Since</h3>
+ <p>{{ runningSince }}</p>
<h3>Number of pipe requests</h3>
- <p>{{ numPipes }}</p>
+ <p>{{ serviceState.numPipes | formatInteger }}</p>
<h3>Total bytes received</h3>
- <p>{{ numBytes }}</p>
+ <p>{{ serviceState.numBytes | formatBytes }}</p>
</div>
- <paper-button class="paper colored red" inkColor="#A9352C" on-click="{{ stopAction }}">Stop Service</paper-button>
+ <paper-button class="paper colored red" inkColor="#A9352C" on-click="{{ stopAction }}">Stop</paper-button>
</template>
<script>
- Polymer('p2b-status', {
+ System.import('libs/utils/formatting').then(function(formatter) {
+ Polymer('p2b-status', {
- /*
- * Dynamic binding for the state of publishing p2b service.
- * Any changes to this object will be reflected in the UI automatically
- */
- serviceState: null,
+ ready: function() {
+ this.runningSince = 'just now';
+ },
- /*
- * A function that can format time duration
- * @private
- * @type {function}
- */
- formatDuration: null,
+ attached: function() {
+ // Update the running since every second.
+ this.runningSinceIntervalId = setInterval(this.updateRunningSince.bind(this), 1000);
+ },
- /*
- * Status text
- * @private
- * @type {string}
- */
- get statusText() {
- if (!this.serviceState) {
- return '';
+ detached: function() {
+ clearInterval(this.runningSinceIntervalId);
+ },
+
+ /*
+ * Dynamic binding for the state of publishing p2b service.
+ * Any changes to this object will be reflected in the UI automatically
+ */
+ serviceState: null,
+
+ /*
+ * Human friendly formatting functions. Because polymer filter expressions
+ * don't accept obj.func we wrap them here
+ * @private
+ */
+ formatDate: formatter.formatDate,
+ formatInteger: formatter.formatInteger,
+ formatBytes: formatter.formatBytes,
+
+ /*
+ * Auto-updating Uptime text
+ * @private
+ * @type {string}
+ */
+ updateRunningSince: function() {
+ if (!this.serviceState) { return; }
+ this.runningSince = formatter.formatRelativeTime(this.serviceState.date);
+ },
+
+ /*
+ * Status text
+ * @private
+ * @type {string}
+ */
+ formatServiceState: function(serviceState) {
+ if (!serviceState) {
+ return '';
+ }
+ if (serviceState.published) {
+ return 'Published';
+ } else if(serviceState.publishing) {
+ return 'Publishing';
+ } else if(serviceState.stopping) {
+ return 'Stopping';
+ } else {
+ return 'Stopped';
+ }
+ },
+
+ /*
+ * Stop action. Fires when user decides to stop the p2b service.
+ * @event
+ */
+ stopAction: function() {
+ this.fire('stop');
}
- if (this.serviceState.published) {
- return 'Published';
- } else if(this.serviceState.publishing) {
- return 'Publishing';
- } else if(this.serviceState.stopping) {
- return 'Stopping';
- } else {
- return 'Stopped';
- }
- },
- /*
- * Formatted publish date
- * @private
- * @type {string}
- */
- get publishDate() {
- if (!this.serviceState || !this.serviceState.published) {
- return '';
- }
- return this.serviceState.date.toString();
- },
-
- /*
- * Formatted uptime
- * @private
- * @type {string}
- */
- get uptime() {
- if (!this.serviceState || !this.serviceState.published) {
- return '';
- }
- var elapsedSeconds = Math.floor((new Date() - this.serviceState.date) / 1000);
- return this.formatDuration(elapsedSeconds);
- },
-
- /*
- * Formatted number of pipes
- * @private
- * @type {string}
- */
- get numPipes() {
- if (!this.serviceState || !this.serviceState.published) {
- return '';
- }
- return this.serviceState.numPipes.toString();
- },
-
- /*
- * Formatted number of bytes
- * @private
- * @type {string}
- */
- get numBytes() {
- if (!this.serviceState || !this.serviceState.published) {
- return '';
- }
- return this.serviceState.numBytes.toString();
- },
-
- /*
- * Stop action. Fires when user decides to stop the p2b service.
- * @event
- */
- stopAction: function() {
- this.fire('stop');
- }
-
+ });
});
</script>
</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/status/view.js b/examples/pipetobrowser/browser/views/status/view.js
index 0f15358..8792b8c 100644
--- a/examples/pipetobrowser/browser/views/status/view.js
+++ b/examples/pipetobrowser/browser/views/status/view.js
@@ -1,5 +1,4 @@
import { View } from 'libs/mvc/view'
-import { formatDuration } from 'libs/utils/time'
/*
* View representing the state and interaction for publishing the p2b service.
@@ -10,11 +9,6 @@
constructor(serviceState) {
var el = document.createElement('p2b-status');
el.serviceState = serviceState;
-
- // TODO(aghassemi) ES6 import syntax doesn't seem to work inside Polymer
- // script tag. Test again when compiling server-side with Traceur.
- el.formatDuration = formatDuration;
-
super(el);
}
diff --git a/examples/pipetobrowser/p2b.vdl b/examples/pipetobrowser/p2b.vdl
index eff991e..3a20904 100644
--- a/examples/pipetobrowser/p2b.vdl
+++ b/examples/pipetobrowser/p2b.vdl
@@ -3,5 +3,5 @@
// Viewer allows clients to stream data to it and to request a particular viewer to format and display the data.
type Viewer interface {
// Pipe creates a bidirectional pipe between client and viewer service, returns total number of bytes received by the service after streaming ends
- Pipe() stream<[]byte, _> (int64, error)
+ Pipe() stream<[]byte, _> (any, error)
}
\ No newline at end of file
diff --git a/examples/pipetobrowser/p2b.vdl.go b/examples/pipetobrowser/p2b.vdl.go
index 422123b..75982ed 100644
--- a/examples/pipetobrowser/p2b.vdl.go
+++ b/examples/pipetobrowser/p2b.vdl.go
@@ -31,28 +31,45 @@
type ViewerService interface {
// Pipe creates a bidirectional pipe between client and viewer service, returns total number of bytes received by the service after streaming ends
- Pipe(context _gen_ipc.ServerContext, stream ViewerServicePipeStream) (reply int64, err error)
+ Pipe(context _gen_ipc.ServerContext, stream ViewerServicePipeStream) (reply _gen_vdlutil.Any, err error)
}
// ViewerPipeStream is the interface for streaming responses of the method
// Pipe in the service interface Viewer.
type ViewerPipeStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
CloseSend() error
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
- Finish() (reply int64, err error)
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
+ Finish() (reply _gen_vdlutil.Any, err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -69,7 +86,7 @@
return c.clientCall.CloseSend()
}
-func (c *implViewerPipeStream) Finish() (reply int64, err error) {
+func (c *implViewerPipeStream) Finish() (reply _gen_vdlutil.Any, err error) {
if ierr := c.clientCall.Finish(&reply, &err); ierr != nil {
err = ierr
}
@@ -208,14 +225,14 @@
result.Methods["Pipe"] = _gen_ipc.MethodSignature{
InArgs: []_gen_ipc.MethodArgument{},
OutArgs: []_gen_ipc.MethodArgument{
- {Name: "", Type: 37},
{Name: "", Type: 65},
+ {Name: "", Type: 66},
},
- InStream: 67,
+ InStream: 68,
}
result.TypeDefs = []_gen_vdlutil.Any{
- _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x42, Name: "", Tags: []string(nil)}}
+ _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x43, Name: "", Tags: []string(nil)}}
return result, nil
}
@@ -238,7 +255,7 @@
return
}
-func (__gen_s *ServerStubViewer) Pipe(call _gen_ipc.ServerCall) (reply int64, err error) {
+func (__gen_s *ServerStubViewer) Pipe(call _gen_ipc.ServerCall) (reply _gen_vdlutil.Any, err error) {
stream := &implViewerServicePipeStream{serverCall: call}
reply, err = __gen_s.service.Pipe(call, stream)
return
diff --git a/examples/pipetobrowser/cli/main.go b/examples/pipetobrowser/p2b/main.go
similarity index 84%
rename from examples/pipetobrowser/cli/main.go
rename to examples/pipetobrowser/p2b/main.go
index 5ecef74..1e47acb 100644
--- a/examples/pipetobrowser/cli/main.go
+++ b/examples/pipetobrowser/p2b/main.go
@@ -8,6 +8,8 @@
"io"
"os"
+ "veyron2"
+ "veyron2/ipc"
"veyron2/rt"
"veyron/examples/pipetobrowser"
@@ -56,7 +58,7 @@
func main() {
flag.Usage = Usage
- runtime := rt.Init()
+ runtime := rt.Init(veyron2.NamespaceRoots{"/proxy.envyor.com:8101"})
log := runtime.Logger()
if flag.NArg() != 1 {
@@ -73,7 +75,7 @@
return
}
- stream, err := s.Pipe(runtime.NewContext())
+ stream, err := s.Pipe(runtime.NewContext(), veyron2.CallTimeout(ipc.NoTimeout))
if err != nil {
log.Errorf("failed to pipe to '%s' please ensure p2b service is running in the browser and name is correct.\nERR:%v", name, err)
return
@@ -83,24 +85,17 @@
stream,
}
- numBytes, err := io.Copy(w, os.Stdin)
+ _, err = io.Copy(w, os.Stdin)
if err != nil {
log.Errorf("failed to copy the stdin pipe to the outgoing stream\nERR:%v", err)
return
}
- stream.CloseSend()
- result, err := stream.Finish()
+ _, err = stream.Finish()
if err != nil {
log.Errorf("error finishing stream: %v", err)
return
}
- if numBytes != result {
- log.Infof("*** number of bytes sent and received do NOT match ***")
- }
- log.Infof("%d bytes were piped to browser", numBytes)
- log.Infof("%d bytes were received by browser", result)
-
fmt.Println("Finished piping to browser! Thanks for using p2b.")
}
diff --git a/examples/pipetobrowser/package.json b/examples/pipetobrowser/package.json
index 8c76a73..add9381 100644
--- a/examples/pipetobrowser/package.json
+++ b/examples/pipetobrowser/package.json
@@ -6,6 +6,6 @@
"jspm": "~0.6.7",
"vulcanize": "~0.3.0",
"serve": "~1.4.0",
- "bower": "~1.3.7"
+ "bower": "~1.3.8"
}
-}
+}
\ No newline at end of file
diff --git a/examples/rockpaperscissors/service.vdl.go b/examples/rockpaperscissors/service.vdl.go
index 268ee07..df5dfa6 100644
--- a/examples/rockpaperscissors/service.vdl.go
+++ b/examples/rockpaperscissors/service.vdl.go
@@ -109,25 +109,43 @@
// Play in the service interface Judge.
type JudgePlayStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
Send(item PlayerAction) 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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item JudgeAction, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (reply PlayResult, err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -164,7 +182,7 @@
// Play in the service interface Judge.
type JudgeServicePlayStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item JudgeAction) error
// Recv fills itemptr with the next item in the input stream, blocking until
diff --git a/examples/todos/test/util.go b/examples/todos/test/util.go
index e3555c9..9f10a45 100644
--- a/examples/todos/test/util.go
+++ b/examples/todos/test/util.go
@@ -21,7 +21,7 @@
// getRuntime initializes the veyron2.Runtime if needed, then returns it.
func getRuntime() veyron2.Runtime {
// returns Runtime if already initialized
- return rt.Init(veyron2.LocalID(security.FakePrivateID("todos")))
+ return rt.Init(veyron2.RuntimeID(security.FakePrivateID("todos")))
}
// startServer starts a store server and returns the server name as well as a
diff --git a/examples/todos/todos_init/main.go b/examples/todos/todos_init/main.go
index 6121a38..44db7c2 100644
--- a/examples/todos/todos_init/main.go
+++ b/examples/todos/todos_init/main.go
@@ -166,7 +166,7 @@
// (since only the admin can put data). The identity here matches with that
// used for server.ServerConfig.Admin in todos_stored/main.go. An alternative
// would be to relax the ACLs on the store.
- rt.Init(veyron2.LocalID(security.FakePrivateID("anonymous")))
+ rt.Init(veyron2.RuntimeID(security.FakePrivateID("anonymous")))
vlog.Infof("Binding to store on %s", storeName)
st, err := vstore.New(storeName)
diff --git a/examples/tunnel/tunnel.vdl.go b/examples/tunnel/tunnel.vdl.go
index 5bc70d2..cd0c593 100644
--- a/examples/tunnel/tunnel.vdl.go
+++ b/examples/tunnel/tunnel.vdl.go
@@ -79,25 +79,43 @@
// Forward in the service interface Tunnel.
type TunnelForwardStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // 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
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -134,7 +152,7 @@
// Forward in the service interface Tunnel.
type TunnelServiceForwardStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item []byte) error
// Recv fills itemptr with the next item in the input stream, blocking until
@@ -160,25 +178,43 @@
// Shell in the service interface Tunnel.
type TunnelShellStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
Send(item ClientShellPacket) 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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item ServerShellPacket, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (reply int32, err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -215,7 +251,7 @@
// Shell in the service interface Tunnel.
type TunnelServiceShellStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item ServerShellPacket) error
// Recv fills itemptr with the next item in the input stream, blocking until
diff --git a/examples/wspr_sample/cache.vdl.go b/examples/wspr_sample/cache.vdl.go
index 5f738a3..5fa6d36 100644
--- a/examples/wspr_sample/cache.vdl.go
+++ b/examples/wspr_sample/cache.vdl.go
@@ -120,25 +120,43 @@
// MultiGet in the service interface Cache.
type CacheMultiGetStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
Send(item string) 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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item _gen_vdlutil.Any, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -175,7 +193,7 @@
// MultiGet in the service interface Cache.
type CacheServiceMultiGetStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item _gen_vdlutil.Any) error
// Recv fills itemptr with the next item in the input stream, blocking until
diff --git a/lib/bluetooth/bluetooth_linux.go b/lib/bluetooth/bluetooth_linux.go
index ed7e378..2a3ba83 100644
--- a/lib/bluetooth/bluetooth_linux.go
+++ b/lib/bluetooth/bluetooth_linux.go
@@ -128,11 +128,7 @@
syscall.Close(int(socket))
return nil, fmt.Errorf("listen error: %v", err)
}
-
- return &listener{
- localAddr: local,
- socket: int(socket),
- }, nil
+ return newListener(int(socket), local)
}
// Dial creates a new RFCOMM connection with the remote address, specified in
@@ -182,11 +178,7 @@
defer C.free(unsafe.Pointer(es))
return nil, fmt.Errorf("dial error: error connecting to remote address: %s, error: %s", remoteAddr, C.GoString(es))
}
- return &conn{
- fd: int(socket),
- localAddr: &local,
- remoteAddr: remote,
- }, nil
+ return newConn(int(socket), &local, remote)
}
// Device is a struct representing an opened Bluetooth device. It consists of
diff --git a/lib/bluetooth/conn.go b/lib/bluetooth/conn.go
index 6b50941..6574313 100644
--- a/lib/bluetooth/conn.go
+++ b/lib/bluetooth/conn.go
@@ -1,3 +1,5 @@
+// +build linux
+
package bluetooth
import (
@@ -9,56 +11,34 @@
// conn represents one RFCOMM connection between two bluetooth devices.
// It implements the net.Conn interface.
-//
-// TODO(ashankar,spetrovic): net.Conn implementations are supposed to be safe
-// for concurrent method invocations. This implementation is not. Fix.
type conn struct {
- fd int
- localAddr, remoteAddr *addr
+ fd *fd
+ localAddr, remoteAddr net.Addr
readDeadline time.Time
writeDeadline time.Time
}
+func newConn(sockfd int, local, remote net.Addr) (net.Conn, error) {
+ fd, err := newFD(sockfd)
+ if err != nil {
+ syscall.Close(sockfd)
+ return nil, err
+ }
+ return &conn{fd: fd, localAddr: local, remoteAddr: remote}, nil
+}
+
func (c *conn) String() string {
return fmt.Sprintf("Bluetooth (%s) <--> (%s)", c.localAddr, c.remoteAddr)
}
-// helper method for Read and Write that ensures:
-// - the returned 'n' is always >= 0, as per guidelines for the io.Reader and
-// io.Writer interfaces.
-func (c *conn) rw(n int, err error) (int, error) {
- if n < 0 {
- n = 0
- }
- return n, err
-}
-
-// Implements the net.Conn interface.
-func (c *conn) Read(p []byte) (n int, err error) {
- return c.rw(syscall.Read(c.fd, p))
-}
-
-// Implements the net.Conn interface.
-func (c *conn) Write(p []byte) (n int, err error) {
- return c.rw(syscall.Write(c.fd, p))
-}
-
-// Implements the net.Conn interface.
-func (c *conn) Close() error {
- return syscall.Close(c.fd)
-}
-
-// Implements the net.Conn interface.
-func (c *conn) LocalAddr() net.Addr {
- return c.localAddr
-}
-
-// Implements the net.Conn interface.
-func (c *conn) RemoteAddr() net.Addr {
- return c.remoteAddr
-}
-
-// Implements the net.Conn interface.
+// net.Conn interface methods
+func (c *conn) Read(p []byte) (n int, err error) { return c.fd.Read(p) }
+func (c *conn) Write(p []byte) (n int, err error) { return c.fd.Write(p) }
+func (c *conn) Close() error { return c.fd.Close() }
+func (c *conn) LocalAddr() net.Addr { return c.localAddr }
+func (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }
+func (c *conn) SetReadDeadline(t time.Time) error { return c.setSockoptTimeval(t, syscall.SO_RCVTIMEO) }
+func (c *conn) SetWriteDeadline(t time.Time) error { return c.setSockoptTimeval(t, syscall.SO_SNDTIMEO) }
func (c *conn) SetDeadline(t time.Time) error {
if err := c.SetReadDeadline(t); err != nil {
return err
@@ -69,18 +49,14 @@
return nil
}
-// Implements the net.Conn interface.
-func (c *conn) SetReadDeadline(t time.Time) error {
- if timeout := getTimeout(t); timeout != nil {
- return syscall.SetsockoptTimeval(c.fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, timeout)
+func (c *conn) setSockoptTimeval(t time.Time, opt int) error {
+ fd, err := c.fd.Reference()
+ if err != nil {
+ return err
}
- return nil
-}
-
-// Implements the net.Conn interface.
-func (c *conn) SetWriteDeadline(t time.Time) error {
+ defer c.fd.ReleaseReference()
if timeout := getTimeout(t); timeout != nil {
- return syscall.SetsockoptTimeval(c.fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, timeout)
+ return syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, opt, timeout)
}
return nil
}
diff --git a/lib/bluetooth/conn_test.go b/lib/bluetooth/conn_test.go
new file mode 100644
index 0000000..8aaa2f1
--- /dev/null
+++ b/lib/bluetooth/conn_test.go
@@ -0,0 +1,87 @@
+// +build linux
+
+package bluetooth
+
+import (
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+// TestConnConcurrency attempts to tests that methods on the *conn type be
+// friendly to concurrent invocation. Unable to figure out a clean way to do
+// this, the author has resorted to just firing up a bunch of goroutines and
+// hoping that failures will manifest often.
+func TestConnConcurrency(t *testing.T) {
+ const (
+ // These numbers were tuned to make the test fail "often"
+ // without the accompanying change to conn.go in the commit
+ // that added this test on the machine that the author was
+ // using at the time.
+ nConcurrentReaders = 30
+ nConcurrentClosers = 10
+ )
+ mp := runtime.GOMAXPROCS(nConcurrentReaders)
+ defer runtime.GOMAXPROCS(mp)
+
+ pipe := func() (rfd, wfd int) {
+ var fds [2]int
+ if err := syscall.Pipe(fds[:]); err != nil {
+ t.Fatal(err)
+ }
+ return fds[0], fds[1]
+ }
+ rfd, wfd := pipe()
+ rConn, _ := newConn(rfd, nil, nil)
+ wConn, _ := newConn(wfd, nil, nil)
+ const (
+ bugs = "bugs bunny"
+ daffy = "daffy duck"
+ )
+ rchan := make(chan string)
+ // Write a bunch of times
+ for i := 0; i < nConcurrentReaders; i++ {
+ go wConn.Write([]byte(bugs))
+ }
+ read := func() {
+ buf := make([]byte, len(bugs))
+ if n, err := rConn.Read(buf); err == nil {
+ rchan <- string(buf[:n])
+ return
+ }
+ rchan <- ""
+ }
+ // Fire up half the readers before Close
+ for i := 0; i < nConcurrentReaders; i += 2 {
+ go read()
+ }
+ // Fire up the closers (and attempt to reassign the file descriptors to
+ // something new).
+ for i := 0; i < nConcurrentClosers; i++ {
+ go func() {
+ rConn.Close()
+ // Create new FDs, which may re-use the closed file descriptors
+ // and write something else to them.
+ rfd, wfd := pipe()
+ syscall.Write(wfd, []byte(daffy))
+ syscall.Close(wfd)
+ syscall.Close(rfd)
+ }()
+ }
+ // And then the remaining readers
+ for i := 1; i < nConcurrentReaders; i += 2 {
+ go read()
+ }
+ // Now read from the channel, should either see full bugs bunnies or empty strings.
+ nEmpty := 0
+ for i := 0; i < nConcurrentReaders; i++ {
+ got := <-rchan
+ switch {
+ case len(got) == 0:
+ nEmpty++
+ case got != bugs:
+ t.Errorf("Read %q, wanted %q or empty string", got, bugs)
+ }
+ }
+ t.Logf("Read returned non-empty %d/%d times", (nConcurrentReaders - nEmpty), nConcurrentReaders)
+}
diff --git a/lib/bluetooth/fd.go b/lib/bluetooth/fd.go
new file mode 100644
index 0000000..328c351
--- /dev/null
+++ b/lib/bluetooth/fd.go
@@ -0,0 +1,219 @@
+// +build linux
+
+package bluetooth
+
+// #include <stddef.h>
+// #include <sys/eventfd.h>
+// #include <sys/select.h>
+//
+// int add_to_eventfd(int fd) {
+// uint64_t val = 1;
+// return write(fd, &val, 8);
+// }
+//
+// int wait(int eventfd, int readfd, int writefd) {
+// fd_set readfds, writefds;
+// FD_ZERO(&readfds);
+// FD_ZERO(&writefds);
+// fd_set* writefdsp = NULL;
+//
+// FD_SET(eventfd, &readfds);
+// int nfds = eventfd + 1;
+//
+// if (readfd >= 0) {
+// FD_SET(readfd, &readfds);
+// if (readfd >= nfds) {
+// nfds = readfd + 1;
+// }
+// }
+// if (writefd >= 0) {
+// FD_SET(writefd, &writefds);
+// if (writefd >= nfds) {
+// nfds = writefd + 1;
+// }
+// writefdsp = &writefds;
+// }
+// // TODO(ashankar): Should EINTR be handled by a retry?
+// // See "Select Law" section of "man 2 select_tut".
+// int nready = select(nfds, &readfds, writefdsp, NULL, NULL);
+// return nready >= 0 && (FD_ISSET(readfd, &readfds) || FD_ISSET(writefd, &writefds));
+// }
+import "C"
+
+import (
+ "fmt"
+ "io"
+ "sync"
+ "syscall"
+)
+
+// An fd enables concurrent invocations of Read, Write and Close on a file
+// descriptor.
+//
+// It ensures that read, write and close operations do not conflict and thereby
+// avoids races between file descriptors being closed and re-used while a
+// read/write is being initiated.
+//
+// This is achieved by using an eventfd to signal the intention to close a
+// descriptor and a select over the eventfd and the file descriptor being
+// protected.
+type fd struct {
+ mu sync.Mutex
+ datafd, eventfd C.int
+ closing bool // Whether Close has been or is being invoked.
+ done *sync.Cond // Signaled when no Read or Writes are pending.
+ refcnt int
+}
+
+// newFD creates an fd object providing read, write and close operations
+// over datafd that are not hostile to concurrent invocations.
+func newFD(datafd int) (*fd, error) {
+ eventfd, err := C.eventfd(0, C.EFD_CLOEXEC)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create eventfd: %v", err)
+ }
+ ret := &fd{datafd: C.int(datafd), eventfd: eventfd}
+ ret.done = sync.NewCond(&ret.mu)
+ return ret, nil
+}
+
+func (fd *fd) Read(p []byte) (int, error) {
+ e, d, err := fd.prepare()
+ if err != nil {
+ return 0, err
+ }
+ defer fd.finish()
+ if err := wait(e, d, -1); err != nil {
+ return 0, err
+ }
+ return fd.rw(syscall.Read(int(fd.datafd), p))
+}
+
+func (fd *fd) Write(p []byte) (int, error) {
+ e, d, err := fd.prepare()
+ if err != nil {
+ return 0, err
+ }
+ defer fd.finish()
+ if err := wait(e, -1, d); err != nil {
+ return 0, err
+ }
+ return fd.rw(syscall.Write(int(fd.datafd), p))
+}
+
+// RunWhenReadable invokes f(file descriptor) when the file descriptor is ready
+// to be read. It returns an error if the file descriptor has been closed
+// either before or while this method is being invoked.
+//
+// f must NOT close readfd.
+func (fd *fd) RunWhenReadable(f func(readfd int)) error {
+ e, d, err := fd.prepare()
+ if err != nil {
+ return err
+ }
+ defer fd.finish()
+ if err := wait(e, d, -1); err != nil {
+ return err
+ }
+ f(int(d))
+ return nil
+}
+
+// Reference returns the underlying file descriptor and ensures that calls to
+// Close will block until ReleaseReference has been called.
+//
+// Clients must NOT close the returned file descriptor.
+func (fd *fd) Reference() (int, error) {
+ fd.mu.Lock()
+ defer fd.mu.Unlock()
+ if fd.closing {
+ return -1, fmt.Errorf("closing")
+ }
+ if fd.datafd < 0 {
+ return -1, fmt.Errorf("closed")
+ }
+ fd.refcnt++
+ return int(fd.datafd), nil
+}
+
+// ReleaseReference returns a reference to the file descriptor grabbed by a
+// call to Reference, thereby unblocking any Close operations.
+func (fd *fd) ReleaseReference() { fd.finish() }
+
+// helper method for Read and Write that ensures:
+// - the returned 'n' is always >= 0, as per guidelines for the io.Reader and
+// io.Writer interfaces.
+func (fd *fd) rw(n int, err error) (int, error) {
+ if n == 0 && err == nil {
+ err = io.EOF
+ }
+ if n < 0 {
+ n = 0
+ }
+ return n, err
+}
+
+func (fd *fd) prepare() (eventfd, datafd C.int, err error) {
+ fd.mu.Lock()
+ defer fd.mu.Unlock()
+ if fd.closing {
+ return 0, 0, fmt.Errorf("closing")
+ }
+ fd.refcnt++
+ // returned file descriptors are guaranteed to be
+ // valid till refcnt is reduced by at least 1, since
+ // Close waits for the refcnt to go down to zero before
+ // closing these file descriptors.
+ return fd.eventfd, fd.datafd, nil
+}
+
+func wait(eventfd, readfd, writefd C.int) error {
+ ok, err := C.wait(eventfd, readfd, writefd)
+ if err != nil {
+ return err
+ }
+ if ok <= 0 {
+ return fmt.Errorf("closing")
+ }
+ return nil
+}
+
+func (fd *fd) finish() {
+ fd.mu.Lock()
+ fd.refcnt--
+ if fd.closing && fd.refcnt == 0 {
+ fd.done.Broadcast()
+ }
+ fd.mu.Unlock()
+}
+
+func (fd *fd) Close() error {
+ fd.mu.Lock()
+ defer fd.mu.Unlock()
+ if !fd.closing {
+ // Send an "event" to notify of closures.
+ if _, err := C.add_to_eventfd(fd.eventfd); err != nil {
+ return fmt.Errorf("failed to notify closure on eventfd: %v", err)
+ }
+ // Prevent any new Read/Write/RunWhenReadable calls from starting.
+ fd.closing = true
+ }
+ for fd.refcnt > 0 {
+ fd.done.Wait()
+ }
+ // At this point, there are no concurrent Read/Write/RunWhenReadable
+ // calls that are using the file descriptors.
+ if fd.eventfd > 0 {
+ if err := syscall.Close(int(fd.eventfd)); err != nil {
+ return fmt.Errorf("failed to close eventfd: %v", err)
+ }
+ fd.eventfd = -1
+ }
+ if fd.datafd > 0 {
+ if err := syscall.Close(int(fd.datafd)); err != nil {
+ return fmt.Errorf("failed to close underlying socket/filedescriptor: %v", err)
+ }
+ fd.datafd = -1
+ }
+ return nil
+}
diff --git a/lib/bluetooth/fd_test.go b/lib/bluetooth/fd_test.go
new file mode 100644
index 0000000..9b7b662
--- /dev/null
+++ b/lib/bluetooth/fd_test.go
@@ -0,0 +1,151 @@
+// +build linux
+
+package bluetooth
+
+import (
+ "fmt"
+ "io"
+ "sort"
+ "syscall"
+ "testing"
+ "time"
+)
+
+// mkfds returns two *fds, one on which Read can be called and one on which
+// Write can be called by using the pipe system call. This pipe is a cheap
+// approximation of a file descriptor backed by a network socket that the fd type
+// is really intended for.
+func mkfds() (readfd, writefd *fd, err error) {
+ var fds [2]int
+ if err = syscall.Pipe(fds[:]); err != nil {
+ err = fmt.Errorf("syscall.Pipe failed: %v", err)
+ return
+ }
+ if readfd, err = newFD(fds[0]); err != nil {
+ err = fmt.Errorf("newFD failed for readfd: %v", err)
+ return
+ }
+ if writefd, err = newFD(fds[1]); err != nil {
+ err = fmt.Errorf("newFD failed for writefd: %v", err)
+ return
+ }
+ return
+}
+
+// canClose calls fd.Close and returns true if fd.Close returns.
+// It returns false if fd.Close blocks.
+// This function uses time to guess whether fd.Close is blocked or
+// not, and is thus not the most accurate implementation. The author
+// welcomes advice on restructuring this function or tests involving
+// it to make the testing deterministically accurate.
+func canClose(fd *fd) bool {
+ c := make(chan error)
+ go func() {
+ c <- fd.Close()
+ }()
+ select {
+ case <-c:
+ return true
+ case <-time.After(time.Millisecond):
+ return false
+ }
+}
+
+func TestFDBasic(t *testing.T) {
+ rfd, wfd, err := mkfds()
+ if err != nil {
+ t.Fatal(err)
+ }
+ const batman = "batman"
+ if n, err := wfd.Write([]byte(batman)); n != 6 || err != nil {
+ t.Errorf("Got (%d, %v) want (6, nil)", n, err)
+ }
+ var read [1024]byte
+ if n, err := rfd.Read(read[:]); n != 6 || err != nil || string(read[:n]) != string(batman) {
+ t.Errorf("Got (%d, %v) = %q, want (6, nil) = %q", n, err, read[:n], batman)
+ }
+ if err := rfd.Close(); err != nil {
+ t.Error(err)
+ }
+ if err := wfd.Close(); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestFDReference(t *testing.T) {
+ fd, _, err := mkfds()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := fd.Reference(); err != nil {
+ t.Fatal(err)
+ }
+ if canClose(fd) {
+ t.Errorf("Should not be able to close fd since there is an outstanding reference")
+ }
+ fd.ReleaseReference()
+ if !canClose(fd) {
+ t.Errorf("Should be able to close fd since there are no outstanding references")
+ }
+}
+
+func TestFDReadEOF(t *testing.T) {
+ rfd, wfd, err := mkfds()
+ if err != nil {
+ t.Fatal(err)
+ }
+ const (
+ bugs = "bugs"
+ bunny = "bunny"
+ )
+ if n, err := wfd.Write([]byte(bugs)); n != len(bugs) || err != nil {
+ t.Fatalf("Got (%d, %v) want (%d, nil)", n, err, len(bugs))
+ }
+ if n, err := wfd.Write([]byte(bunny)); n != len(bunny) || err != nil {
+ t.Fatalf("Got (%d, %v) want (%d, nil)", n, err, len(bunny))
+ }
+ if err := wfd.Close(); err != nil {
+ t.Fatal(err)
+ }
+ var read [1024]byte
+ if n, err := rfd.Read(read[:]); n != len(bugs)+len(bunny) || err != nil {
+ t.Errorf("Got (%d, %v) = %q, want (%d, nil) = %q", n, err, read[:n], len(bugs)+len(bunny), "bugsbunny")
+ }
+ if n, err := rfd.Read(read[:]); n != 0 || err != io.EOF {
+ t.Errorf("Got (%d, %v) = %q, want (0, EOF)", n, err, read[:n])
+ }
+}
+
+func TestFDReadLessThanReady(t *testing.T) {
+ rfd, wfd, err := mkfds()
+ if err != nil {
+ t.Fatal(err)
+ }
+ const nbytes = 20
+ rchan := make(chan int, nbytes)
+ written := make([]byte, nbytes)
+ for i := 1; i <= nbytes; i++ {
+ written[i-1] = byte(i)
+ go func() {
+ var buf [1]byte
+ rfd.Read(buf[:])
+ rchan <- int(buf[0])
+ }()
+ }
+ if n, err := wfd.Write(written); n != nbytes || err != nil {
+ t.Fatal("Got (%d, %v), want (%d, nil)", n, err, nbytes)
+ }
+ if err := wfd.Close(); err != nil {
+ t.Fatal(err)
+ }
+ read := make([]int, nbytes)
+ for i := 0; i < nbytes; i++ {
+ read[i] = <-rchan
+ }
+ sort.Ints(read)
+ for i, v := range read {
+ if i != v-1 {
+ t.Fatalf("Got %v, wanted it to be sorted", read)
+ }
+ }
+}
diff --git a/lib/bluetooth/listener.go b/lib/bluetooth/listener.go
index da383ad..a375a8f 100644
--- a/lib/bluetooth/listener.go
+++ b/lib/bluetooth/listener.go
@@ -5,7 +5,6 @@
import (
"fmt"
"net"
- "syscall"
"unsafe"
)
@@ -20,17 +19,40 @@
// listener waits for incoming RFCOMM connections on the provided socket.
// It implements the net.Listener interface.
type listener struct {
- localAddr *addr
- socket int
+ fd *fd
+ acceptChan chan (acceptResult)
+ localAddr net.Addr
+}
+
+type acceptResult struct {
+ conn net.Conn
+ err error
+}
+
+func newListener(sockfd int, addr net.Addr) (net.Listener, error) {
+ fd, err := newFD(sockfd)
+ if err != nil {
+ return nil, err
+ }
+ return &listener{fd: fd, acceptChan: make(chan acceptResult), localAddr: addr}, nil
}
// Implements the net.Listener interface.
func (l *listener) Accept() (net.Conn, error) {
+ go l.fd.RunWhenReadable(l.accept)
+ r := <-l.acceptChan
+ return r.conn, r.err
+}
+
+func (l *listener) accept(sockfd int) {
var fd C.int
var remoteMAC *C.char
- if es := C.bt_accept(C.int(l.socket), &fd, &remoteMAC); es != nil {
+ var result acceptResult
+ defer func() { l.acceptChan <- result }()
+ if es := C.bt_accept(C.int(sockfd), &fd, &remoteMAC); es != nil {
defer C.free(unsafe.Pointer(es))
- return nil, fmt.Errorf("error accepting connection on %s, socket: %d, error: %s", l.localAddr, l.socket, C.GoString(es))
+ result.err = fmt.Errorf("error accepting connection on %s, socket: %d, error: %s", l.localAddr, sockfd, C.GoString(es))
+ return
}
defer C.free(unsafe.Pointer(remoteMAC))
@@ -38,21 +60,17 @@
var remote addr
var err error
if remote.mac, err = net.ParseMAC(C.GoString(remoteMAC)); err != nil {
- return nil, fmt.Errorf("invalid remote MAC address: %s, err: %s", C.GoString(remoteMAC), err)
+ result.err = fmt.Errorf("invalid remote MAC address: %s, err: %s", C.GoString(remoteMAC), err)
+ return
}
// There's no way to get accurate remote channel number, so use 0.
remote.channel = 0
-
- return &conn{
- fd: int(fd),
- localAddr: l.localAddr,
- remoteAddr: &remote,
- }, nil
+ result.conn, result.err = newConn(int(fd), l.localAddr, &remote)
}
// Implements the net.Listener interface.
func (l *listener) Close() error {
- return syscall.Close(l.socket)
+ return l.fd.Close()
}
// Implements the net.Listener interface.
diff --git a/runtimes/google/ipc/benchmarks/service.vdl.go b/runtimes/google/ipc/benchmarks/service.vdl.go
index 2356536..ed8a343 100644
--- a/runtimes/google/ipc/benchmarks/service.vdl.go
+++ b/runtimes/google/ipc/benchmarks/service.vdl.go
@@ -43,25 +43,43 @@
// 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 places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
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.
+ // 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
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -98,7 +116,7 @@
// 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.
+ // space available. If the client has canceled, an error is returned.
Send(item []byte) error
// Recv fills itemptr with the next item in the input stream, blocking until
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 8850b21..47dfb93 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -9,12 +9,16 @@
"time"
_ "veyron/lib/testutil"
+ isecurity "veyron/runtimes/google/security"
+
"veyron2/ipc"
"veyron2/naming"
"veyron2/security"
"veyron2/verror"
)
+var testID = newID("test")
+
// newTestFlows returns the two ends of a bidirectional flow. Each end has its
// own bookkeeping, to allow testing of method calls.
func newTestFlows() (*testFlow, *testFlow) {
@@ -34,8 +38,8 @@
func (f *testFlow) RemoteAddr() net.Addr { return nil }
func (f *testFlow) LocalEndpoint() naming.Endpoint { return nil }
func (f *testFlow) RemoteEndpoint() naming.Endpoint { return nil }
-func (f *testFlow) LocalID() security.PublicID { return security.FakePublicID("test") }
-func (f *testFlow) RemoteID() security.PublicID { return security.FakePublicID("test") }
+func (f *testFlow) LocalID() security.PublicID { return testID.PublicID() }
+func (f *testFlow) RemoteID() security.PublicID { return testID.PublicID() }
func (f *testFlow) SetReadDeadline(t time.Time) error { return nil }
func (f *testFlow) SetWriteDeadline(t time.Time) error { return nil }
func (f *testFlow) SetDeadline(t time.Time) error { return nil }
@@ -139,3 +143,7 @@
}
}
}
+
+func init() {
+ isecurity.TrustIdentityProviders(testID)
+}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 9b24996..03a3d5a 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -39,8 +39,8 @@
var (
errAuthorizer = errors.New("ipc: application Authorizer denied access")
errMethod = verror.Abortedf("server returned an error")
- clientID security.PrivateID
- serverID security.PrivateID
+ clientID = newID("client")
+ serverID = newID("server")
clock = new(fakeClock)
)
@@ -259,7 +259,7 @@
func startServer(t *testing.T, serverID security.PrivateID, sm stream.Manager, ns naming.Namespace, ts interface{}) (naming.Endpoint, ipc.Server) {
vlog.VI(1).Info("InternalNewServer")
- server, err := InternalNewServer(InternalNewContext(), sm, ns, listenerID(serverID))
+ server, err := InternalNewServer(InternalNewContext(), sm, ns, vc.FixedLocalID(serverID))
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -327,18 +327,25 @@
}
func (b bundle) cleanup(t *testing.T) {
- stopServer(t, b.server, b.ns)
- b.client.Close()
+ if b.server != nil {
+ stopServer(t, b.server, b.ns)
+ }
+ if b.client != nil {
+ b.client.Close()
+ }
}
func createBundle(t *testing.T, clientID, serverID security.PrivateID, ts interface{}) (b bundle) {
b.sm = imanager.InternalNew(naming.FixedRoutingID(0x555555555))
b.ns = newNamespace()
- b.ep, b.server = startServer(t, serverID, b.sm, b.ns, ts)
- var err error
- b.client, err = InternalNewClient(b.sm, b.ns, veyron2.LocalID(clientID))
- if err != nil {
- t.Fatalf("InternalNewClient failed: %v", err)
+ if serverID != nil {
+ b.ep, b.server = startServer(t, serverID, b.sm, b.ns, ts)
+ }
+ if clientID != nil {
+ var err error
+ if b.client, err = InternalNewClient(b.sm, b.ns, vc.FixedLocalID(clientID)); err != nil {
+ t.Fatalf("InternalNewClient failed: %v", err)
+ }
}
return
}
@@ -352,10 +359,7 @@
}
func derive(blessor security.PrivateID, name string, caveats ...security.ServiceCaveat) security.PrivateID {
- id, err := isecurity.NewPrivateID("irrelevant")
- if err != nil {
- panic(err)
- }
+ id := newID("irrelevant")
derivedID, err := id.Derive(bless(blessor, id.PublicID(), name, caveats...))
if err != nil {
panic(err)
@@ -394,7 +398,7 @@
func TestMultipleCallsToServe(t *testing.T) {
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
ns := newNamespace()
- server, err := InternalNewServer(InternalNewContext(), sm, ns, listenerID(serverID))
+ server, err := InternalNewServer(InternalNewContext(), sm, ns, vc.FixedLocalID(serverID))
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -484,7 +488,7 @@
for _, test := range tests {
name := fmt.Sprintf("(clientID:%q serverID:%q)", test.clientID, test.serverID)
_, server := startServer(t, test.serverID, mgr, ns, &testServer{})
- client, err := InternalNewClient(mgr, ns, veyron2.LocalID(test.clientID))
+ client, err := InternalNewClient(mgr, ns, vc.FixedLocalID(test.clientID))
if err != nil {
t.Errorf("%s: Client creation failed: %v", name, err)
stopServer(t, server, ns)
@@ -715,10 +719,10 @@
return fmt.Sprintf("%q RPCing %s.%s(%v)", t.clientID.PublicID(), t.name, t.method, t.args)
}
- b := createBundle(t, nil, serverID, &testServer{})
+ b := createBundle(t, nil, serverID, &testServer{}) // we only create the server, a separate client will be created for each test.
defer b.cleanup(t)
for _, test := range tests {
- client, err := InternalNewClient(b.sm, b.ns, veyron2.LocalID(test.clientID))
+ client, err := InternalNewClient(b.sm, b.ns, vc.FixedLocalID(test.clientID))
if err != nil {
t.Fatalf("InternalNewClient failed: %v", err)
}
@@ -969,7 +973,7 @@
{[]ipc.ServerOpt{veyron2.PublishFirst, veyron2.EndpointRewriteOpt("example.com")}, []string{"example.com"}},
}
for i, c := range cases {
- server, err := InternalNewServer(InternalNewContext(), sm, ns, append([]ipc.ServerOpt{listenerID(serverID)}, c.opts...)...)
+ server, err := InternalNewServer(InternalNewContext(), sm, ns, append(c.opts, vc.FixedLocalID(serverID))...)
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
continue
@@ -1102,12 +1106,12 @@
func TestProxy(t *testing.T) {
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
ns := newNamespace()
- client, err := InternalNewClient(sm, ns, veyron2.LocalID(clientID))
+ client, err := InternalNewClient(sm, ns, vc.FixedLocalID(clientID))
if err != nil {
t.Fatal(err)
}
defer client.Close()
- server, err := InternalNewServer(InternalNewContext(), sm, ns, listenerID(serverID))
+ server, err := InternalNewServer(InternalNewContext(), sm, ns, vc.FixedLocalID(serverID))
if err != nil {
t.Fatal(err)
}
@@ -1189,7 +1193,7 @@
ns := newNamespace()
id := loadIdentityFromFile(argv[1])
isecurity.TrustIdentityProviders(id)
- server, err := InternalNewServer(InternalNewContext(), mgr, ns, listenerID(id))
+ server, err := InternalNewServer(InternalNewContext(), mgr, ns, vc.FixedLocalID(id))
if err != nil {
vlog.Fatalf("InternalNewServer failed: %v", err)
}
@@ -1225,13 +1229,6 @@
}
func init() {
- var err error
- if clientID, err = isecurity.NewPrivateID("client"); err != nil {
- vlog.Fatalf("failed isecurity.NewPrivateID: %s", err)
- }
- if serverID, err = isecurity.NewPrivateID("server"); err != nil {
- vlog.Fatalf("failed isecurity.NewPrivateID: %s", err)
- }
isecurity.TrustIdentityProviders(clientID)
isecurity.TrustIdentityProviders(serverID)
diff --git a/runtimes/google/ipc/stream/crypto/crypto_test.go b/runtimes/google/ipc/stream/crypto/crypto_test.go
index 4bbdd45..84b0d76 100644
--- a/runtimes/google/ipc/stream/crypto/crypto_test.go
+++ b/runtimes/google/ipc/stream/crypto/crypto_test.go
@@ -37,8 +37,7 @@
crypter.String() // Only to test that String does not crash.
}
-func testSimple(t *testing.T, crypters func(testing.TB) (Crypter, Crypter)) {
- c1, c2 := crypters(t)
+func testSimple(t *testing.T, c1, c2 Crypter) {
// Execute String just to check that it does not crash.
c1.String()
c2.String()
@@ -63,11 +62,21 @@
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 TestTLS(t *testing.T) {
+ server, client := net.Pipe()
+ c1, c2 := tlsCrypters(t, server, client)
+ testSimple(t, c1, c2)
+}
+
+func TestBox(t *testing.T) {
+ server, client := net.Pipe()
+ c1, c2 := boxCrypters(t, server, client)
+ testSimple(t, c1, c2)
+}
func TestTLSNil(t *testing.T) {
- c1, c2 := tlsCrypters(t)
+ conn1, conn2 := net.Pipe()
+ c1, c2 := tlsCrypters(t, conn1, conn2)
if t.Failed() {
return
}
@@ -89,7 +98,8 @@
// 16K (it is represented by a uint16).
// http://tools.ietf.org/html/rfc5246#section-6.2.1
const dataLen = 16384 + 1
- enc, dec := tlsCrypters(t)
+ conn1, conn2 := net.Pipe()
+ enc, dec := tlsCrypters(t, conn1, conn2)
cipher, err := enc.Encrypt(iobuf.NewSlice(make([]byte, dataLen)))
if err != nil {
t.Fatal(err)
@@ -103,8 +113,9 @@
}
}
-func tlsCrypters(t testing.TB) (Crypter, Crypter) {
- serverConn, clientConn := net.Pipe()
+type factory func(t testing.TB, server, client net.Conn) (Crypter, Crypter)
+
+func tlsCrypters(t testing.TB, serverConn, clientConn net.Conn) (Crypter, Crypter) {
crypters := make(chan Crypter)
go func() {
server, err := NewTLSServer(serverConn, iobuf.NewPool(0))
@@ -126,8 +137,7 @@
return c1, c2
}
-func boxCrypters(t testing.TB) (Crypter, Crypter) {
- serverConn, clientConn := net.Pipe()
+func boxCrypters(t testing.TB, serverConn, clientConn net.Conn) (Crypter, Crypter) {
crypters := make(chan Crypter)
for _, conn := range []net.Conn{serverConn, clientConn} {
go func(conn net.Conn) {
@@ -141,12 +151,15 @@
return <-crypters, <-crypters
}
-func benchmarkEncrypt(b *testing.B, crypters func(testing.TB) (Crypter, Crypter), size int) {
+func benchmarkEncrypt(b *testing.B, crypters factory, size int) {
plaintext := make([]byte, size)
if _, err := rand.Read(plaintext); err != nil {
b.Fatal(err)
}
- e, _ := crypters(b)
+ conn1, conn2 := net.Pipe()
+ defer conn1.Close()
+ defer conn2.Close()
+ e, _ := crypters(b, conn1, conn2)
b.SetBytes(int64(size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -170,12 +183,15 @@
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) {
+func benchmarkRoundTrip(b *testing.B, crypters factory, size int) {
plaintext := make([]byte, size)
if _, err := rand.Read(plaintext); err != nil {
b.Fatal(err)
}
- e, d := tlsCrypters(b)
+ conn1, conn2 := net.Pipe()
+ defer conn1.Close()
+ defer conn2.Close()
+ e, d := crypters(b, conn1, conn2)
b.SetBytes(int64(size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -201,3 +217,15 @@
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) }
+
+func benchmarkSetup(b *testing.B, crypters factory) {
+ for i := 0; i < b.N; i++ {
+ conn1, conn2 := net.Pipe()
+ crypters(b, conn1, conn2)
+ conn1.Close()
+ conn2.Close()
+ }
+}
+
+func BenchmarkTLSSetup(b *testing.B) { benchmarkSetup(b, tlsCrypters) }
+func BenchmarkBoxSetup(b *testing.B) { benchmarkSetup(b, boxCrypters) }
diff --git a/runtimes/google/ipc/stream/manager/manager_test.go b/runtimes/google/ipc/stream/manager/manager_test.go
index 943a667..2e02ffb 100644
--- a/runtimes/google/ipc/stream/manager/manager_test.go
+++ b/runtimes/google/ipc/stream/manager/manager_test.go
@@ -15,14 +15,22 @@
"veyron/runtimes/google/ipc/stream/vc"
"veyron/runtimes/google/ipc/version"
inaming "veyron/runtimes/google/naming"
+ isecurity "veyron/runtimes/google/security"
- "veyron2"
"veyron2/ipc/stream"
"veyron2/naming"
"veyron2/security"
"veyron2/vlog"
)
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
func init() {
// The testutil package's init sets GOMAXPROCS to NumCPU. We want to
// force GOMAXPROCS to remain at 1, in order to trigger a particular
@@ -121,12 +129,11 @@
server := InternalNew(naming.FixedRoutingID(0x55555555))
client := InternalNew(naming.FixedRoutingID(0xcccccccc))
- serverID := security.FakePrivateID("server")
- clientID := security.FakePrivateID("client")
-
+ clientID := newID("client")
+ serverID := newID("server")
// VCSecurityLevel is intentionally not provided to Listen - to test
// default behavior.
- ln, ep, err := server.Listen("tcp", "localhost:0", vc.ListenerID(serverID))
+ ln, ep, err := server.Listen("tcp", "localhost:0", vc.FixedLocalID(serverID))
if err != nil {
t.Fatal(err)
}
@@ -156,7 +163,7 @@
go func() {
// VCSecurityLevel is intentionally not provided to Dial - to
// test default behavior.
- vc, err := client.Dial(ep, veyron2.LocalID(clientID))
+ vc, err := client.Dial(ep, vc.FixedLocalID(clientID))
if err != nil {
errs <- err
return
@@ -287,16 +294,14 @@
}
func TestSessionTicketCache(t *testing.T) {
- serverID := vc.ListenerID(security.FakePrivateID("TestSessionTicketCacheServer"))
server := InternalNew(naming.FixedRoutingID(0x55555555))
- _, ep, err := server.Listen("tcp", "localhost:0", serverID)
+ _, ep, err := server.Listen("tcp", "localhost:0", vc.FixedLocalID(newID("server")))
if err != nil {
t.Fatal(err)
}
- clientID := veyron2.LocalID(security.FakePrivateID("TestSessionTicketCacheClient"))
client := InternalNew(naming.FixedRoutingID(0xcccccccc))
- if _, err = client.Dial(ep, clientID); err != nil {
+ if _, err = client.Dial(ep, vc.FixedLocalID(newID("TestSessionTicketCacheClient"))); err != nil {
t.Fatalf("Dial(%q) failed: %v", ep, err)
}
@@ -314,7 +319,7 @@
// Have the server read from each flow and write to rchan.
rchan := make(chan string)
- ln, ep, err := server.Listen("tcp", "localhost:0", vc.ListenerID(security.FakePrivateID("server")))
+ ln, ep, err := server.Listen("tcp", "localhost:0", vc.FixedLocalID(newID("server")))
if err != nil {
t.Fatal(err)
}
@@ -350,7 +355,7 @@
var vcs [nVCs]stream.VC
for i := 0; i < nVCs; i++ {
var err error
- vcs[i], err = client.Dial(ep, veyron2.LocalID(security.FakePrivateID("client")))
+ vcs[i], err = client.Dial(ep, vc.FixedLocalID(newID("client")))
if err != nil {
t.Fatal(err)
}
@@ -399,7 +404,7 @@
}
go acceptLoop(ln)
- // We'd like an enpoint that contains an address that's different
+ // We'd like an endpoint that contains an address that's different
// to the one used for the connection. In practice this is awkward
// to achieve since we don't want to listen on ":0" since that will
// annoy firewalls. Instead we listen on 127.0.0.1 and we fabricate an
@@ -465,7 +470,7 @@
func runServer(argv []string) {
server := InternalNew(naming.FixedRoutingID(0x55555555))
- _, ep, err := server.Listen("tcp", argv[0], vc.ListenerID(security.FakePrivateID("server")))
+ _, ep, err := server.Listen("tcp", argv[0], vc.FixedLocalID(newID("server")))
if err != nil {
fmt.Println(err)
return
diff --git a/runtimes/google/ipc/stream/proxy/proxy.go b/runtimes/google/ipc/stream/proxy/proxy.go
index e0022d5..433ad73 100644
--- a/runtimes/google/ipc/stream/proxy/proxy.go
+++ b/runtimes/google/ipc/stream/proxy/proxy.go
@@ -31,7 +31,7 @@
type Proxy struct {
ln net.Listener
rid naming.RoutingID
- id security.PrivateID
+ id vc.LocalID
mu sync.RWMutex
servers *servermap
processes map[*process]struct{}
@@ -135,14 +135,17 @@
proxy := &Proxy{
ln: ln,
rid: rid,
- id: identity,
servers: &servermap{m: make(map[naming.RoutingID]*server)},
processes: make(map[*process]struct{}),
pubAddress: pubAddress,
}
+ if identity != nil {
+ proxy.id = vc.FixedLocalID(identity)
+ }
go proxy.listenLoop()
return proxy, nil
}
+
func (p *Proxy) listenLoop() {
proxyLog().Infof("Proxy listening on (%q, %q): %v", p.ln.Addr().Network(), p.ln.Addr(), p.Endpoint())
for {
@@ -313,7 +316,7 @@
p.routeCounters(process, m.Counters)
if vcObj != nil {
server := &server{Process: process, VC: vcObj}
- go p.runServer(server, vcObj.HandshakeAcceptedVC(vc.ListenerID(p.id)))
+ go p.runServer(server, vcObj.HandshakeAcceptedVC(p.id))
}
break
}
diff --git a/runtimes/google/ipc/stream/proxy/proxy_test.go b/runtimes/google/ipc/stream/proxy/proxy_test.go
index c28e618..7b3e1dd 100644
--- a/runtimes/google/ipc/stream/proxy/proxy_test.go
+++ b/runtimes/google/ipc/stream/proxy/proxy_test.go
@@ -4,6 +4,7 @@
"bytes"
"fmt"
"io"
+ "reflect"
"strings"
"testing"
@@ -11,12 +12,21 @@
"veyron/runtimes/google/ipc/stream/manager"
"veyron/runtimes/google/ipc/stream/proxy"
"veyron/runtimes/google/ipc/stream/vc"
+ isecurity "veyron/runtimes/google/security"
"veyron2/ipc/stream"
"veyron2/naming"
"veyron2/security"
)
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
func TestProxy(t *testing.T) {
// Using "tcp4" instead of "tcp" because the latter can end up with
// IPv6 addresses and our Google Compute Engine integration test
@@ -110,7 +120,8 @@
}
func TestProxyIdentity(t *testing.T) {
- proxy, err := proxy.New(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), security.FakePrivateID("proxy"), "tcp4", "127.0.0.1:0", "")
+ proxyID := newID("proxy")
+ proxy, err := proxy.New(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), proxyID, "tcp4", "127.0.0.1:0", "")
if err != nil {
t.Fatal(err)
}
@@ -128,7 +139,7 @@
if err != nil {
t.Fatal(err)
}
- if got, want := fmt.Sprintf("%v", flow.RemoteID()), "fake/proxy"; got != want {
+ if got, want := fmt.Sprintf("%v", flow.RemoteID()), fmt.Sprintf("%v", proxyID.PublicID()); got != want {
t.Errorf("Proxy has identity %q want %q", flow.RemoteID(), want)
}
}
@@ -141,8 +152,11 @@
server := manager.InternalNew(naming.FixedRoutingID(0x5555555555555555))
defer server.Shutdown()
- serverID := security.FakePrivateID("server")
- ln, ep, err := server.Listen(proxy.Endpoint().Network(), proxy.Endpoint().String(), vc.ListenerID(serverID))
+ serverID := newID("server")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ln, ep, err := server.Listen(proxy.Endpoint().Network(), proxy.Endpoint().String(), vc.FixedLocalID(serverID))
if err != nil {
t.Fatal(err)
}
@@ -165,7 +179,7 @@
if err != nil {
t.Fatal(err)
}
- if got, want := fmt.Sprintf("%v", flow.RemoteID()), "fake/server"; got != want {
+ if got, want := flow.RemoteID(), serverID.PublicID(); !reflect.DeepEqual(got, want) {
t.Errorf("Got %q want %q", got, want)
}
}
diff --git a/runtimes/google/ipc/stream/vc/auth.go b/runtimes/google/ipc/stream/vc/auth.go
index 0378b31..0d4b094 100644
--- a/runtimes/google/ipc/stream/vc/auth.go
+++ b/runtimes/google/ipc/stream/vc/auth.go
@@ -21,15 +21,19 @@
)
// authenticateAsServer executes the authentication protocol at the server and
-// returns the identity of the client.
-func authenticateAsServer(conn io.ReadWriteCloser, serverID security.PrivateID, crypter crypto.Crypter) (clientID security.PublicID, err error) {
+// returns the identity of the client and server.
+func authenticateAsServer(conn io.ReadWriteCloser, localID LocalID, crypter crypto.Crypter) (clientID, serverID security.PublicID, err error) {
// The authentication protocol has the server doing the final read, so
// it is the one that closes the connection.
defer conn.Close()
- if err = writeIdentity(conn, serverChannelEnd, crypter, serverID); err != nil {
+ if serverID, err = localID.AsServer(); err != nil {
return
}
- return readIdentity(conn, clientChannelEnd, crypter)
+ if err = writeIdentity(conn, serverChannelEnd, crypter, localID, serverID); err != nil {
+ return
+ }
+ clientID, err = readIdentity(conn, clientChannelEnd, crypter)
+ return
}
// authenticateAsClient executes the authentication protocol at the client and
@@ -37,12 +41,17 @@
//
// If serverName is non-nil, the authentication protocol will be considered
// successfull iff the server identity matches the provided regular expression.
-func authenticateAsClient(conn io.ReadWriteCloser, clientID security.PrivateID, crypter crypto.Crypter) (serverID security.PublicID, err error) {
+func authenticateAsClient(conn io.ReadWriteCloser, localID LocalID, crypter crypto.Crypter) (serverID, clientID security.PublicID, err error) {
defer conn.Close()
if serverID, err = readIdentity(conn, serverChannelEnd, crypter); err != nil {
return
}
- err = writeIdentity(conn, clientChannelEnd, crypter, clientID)
+ // TODO(ashankar,ataly): Have the ability to avoid talking to a server we do not want to.
+ // Will require calling Authorize on the server id?
+ if clientID, err = localID.AsClient(serverID); err != nil {
+ return
+ }
+ err = writeIdentity(conn, clientChannelEnd, crypter, localID, clientID)
return
}
@@ -64,7 +73,7 @@
errSingleCertificateRequired = errors.New("exactly one X.509 certificate chain with exactly one certificate is required")
)
-func writeIdentity(w io.Writer, chEnd string, enc crypto.Encrypter, id security.PrivateID) error {
+func writeIdentity(w io.Writer, chEnd string, enc crypto.Encrypter, id LocalID, pub security.PublicID) error {
// Compute channel id - encrypted chEnd string
chid, err := enc.Encrypt(iobuf.NewSlice([]byte(chEnd)))
if err != nil {
@@ -74,7 +83,7 @@
// VOM-encode and encrypt the (public) identity.
var buf bytes.Buffer
- if err := vom.NewEncoder(&buf).Encode(id.PublicID()); err != nil {
+ if err := vom.NewEncoder(&buf).Encode(pub); err != nil {
return err
}
eid, err := enc.Encrypt(iobuf.NewSlice(buf.Bytes()))
diff --git a/runtimes/google/ipc/stream/vc/init.go b/runtimes/google/ipc/stream/vc/init.go
new file mode 100644
index 0000000..a801353
--- /dev/null
+++ b/runtimes/google/ipc/stream/vc/init.go
@@ -0,0 +1,17 @@
+package vc
+
+import (
+ isecurity "veyron/runtimes/google/security"
+
+ "veyron2/security"
+ "veyron2/vlog"
+)
+
+var anonymousID security.PrivateID
+
+func init() {
+ var err error
+ if anonymousID, err = isecurity.NewPrivateID("anonymous"); err != nil {
+ vlog.Fatalf("could create anonymousID for IPCs: %s", err)
+ }
+}
diff --git a/runtimes/google/ipc/stream/vc/listener_test.go b/runtimes/google/ipc/stream/vc/listener_test.go
index a449242..a621b97 100644
--- a/runtimes/google/ipc/stream/vc/listener_test.go
+++ b/runtimes/google/ipc/stream/vc/listener_test.go
@@ -5,12 +5,24 @@
"testing"
"time"
+ isecurity "veyron/runtimes/google/security"
+
"veyron2/naming"
"veyron2/security"
)
+var testID = newID("test")
+
type noopFlow struct{}
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
// net.Conn methods
func (*noopFlow) Read([]byte) (int, error) { return 0, nil }
func (*noopFlow) Write([]byte) (int, error) { return 0, nil }
@@ -27,8 +39,8 @@
func (*noopFlow) SetWriteDeadline(t time.Time) error { return nil }
// Other stream.Flow methods
-func (*noopFlow) LocalID() security.PublicID { return security.FakePublicID("test") }
-func (*noopFlow) RemoteID() security.PublicID { return security.FakePublicID("test") }
+func (*noopFlow) LocalID() security.PublicID { return testID.PublicID() }
+func (*noopFlow) RemoteID() security.PublicID { return testID.PublicID() }
func TestListener(t *testing.T) {
ln := newListener()
diff --git a/runtimes/google/ipc/stream/vc/vc.go b/runtimes/google/ipc/stream/vc/vc.go
index 09d719d..7f70721 100644
--- a/runtimes/google/ipc/stream/vc/vc.go
+++ b/runtimes/google/ipc/stream/vc/vc.go
@@ -90,27 +90,48 @@
Helper Helper
}
-// ListenerIDOpt is the interface for providing an identity to an ipc.StreamListener.
-type ListenerIDOpt interface {
+// LocalID is the interface for providing a PrivateID and a PublicIDStore to
+// be used at the local end of VCs.
+type LocalID interface {
stream.ListenerOpt
- // Identity returns the identity to be used by the ipc.StreamListener.
- Identity() security.PrivateID
+ stream.VCOpt
+ // Sign signs an arbitrary length message (often the hash of a larger message)
+ // using a private key.
+ Sign(message []byte) (security.Signature, error)
+
+ // AsClient returns a PublicID to be used while authenticating as a client to the
+ // provided server as a client. An error is returned if no such PublicID can be returned.
+ AsClient(server security.PublicID) (security.PublicID, error)
+
+ // AsServer returns a PublicID to be used while authenticating as a server to other
+ // clients. An error is returned if no such PublicID can be returned.
+ AsServer() (security.PublicID, error)
+ IPCClientOpt()
+ IPCServerOpt()
}
-// listenerIDOpt implements ListenerIDOpt.
-type listenerIDOpt struct {
- id security.PrivateID
+// fixedLocalID implements vc.LocalID.
+type fixedLocalID struct {
+ security.PrivateID
}
-func (opt *listenerIDOpt) Identity() security.PrivateID {
- return opt.id
+func (f fixedLocalID) AsClient(security.PublicID) (security.PublicID, error) {
+ return f.PrivateID.PublicID(), nil
}
-func (*listenerIDOpt) IPCStreamListenerOpt() {}
+func (f fixedLocalID) AsServer() (security.PublicID, error) {
+ return f.PrivateID.PublicID(), nil
+}
-// ListenerID provides an implementation of ListenerIDOpt with a fixed identity.
-func ListenerID(id security.PrivateID) ListenerIDOpt {
- return &listenerIDOpt{id}
+func (fixedLocalID) IPCStreamListenerOpt() {}
+func (fixedLocalID) IPCStreamVCOpt() {}
+func (fixedLocalID) IPCClientOpt() {}
+func (fixedLocalID) IPCServerOpt() {}
+
+// FixedLocalID creates a LocalID using the provided PrivateID. The
+// provided PrivateID must always be non-nil.
+func FixedLocalID(id security.PrivateID) LocalID {
+ return fixedLocalID{id}
}
// InternalNew creates a new VC, which implements the stream.VC interface.
@@ -347,13 +368,13 @@
// authentication etc.) under the assumption that the VC was initiated by the
// local process (i.e., the local process "Dial"ed to create the VC).
func (vc *VC) HandshakeDialedVC(opts ...stream.VCOpt) error {
- var localID security.PrivateID
+ var localID LocalID
var tlsSessionCache tls.ClientSessionCache
var securityLevel veyron2.VCSecurityLevel
for _, o := range opts {
switch v := o.(type) {
- case veyron2.LocalIDOpt:
- localID = v.PrivateID
+ case LocalID:
+ localID = v
case veyron2.VCSecurityLevel:
securityLevel = v
case crypto.TLSClientSessionCache:
@@ -362,7 +383,9 @@
}
switch securityLevel {
case veyron2.VCSecurityConfidential:
- localID = anonymousIfNilPrivateID(localID)
+ if localID == nil {
+ localID = FixedLocalID(anonymousID)
+ }
case veyron2.VCSecurityNone:
return nil
default:
@@ -394,7 +417,7 @@
if err != nil {
return vc.err(fmt.Errorf("failed to create a Flow for authentication: %v", err))
}
- remoteID, err := authenticateAsClient(authConn, localID, crypter)
+ rID, lID, err := authenticateAsClient(authConn, localID, crypter)
if err != nil {
return vc.err(fmt.Errorf("authentication failed: %v", err))
}
@@ -403,11 +426,11 @@
vc.handshakeFID = handshakeFID
vc.authFID = authFID
vc.crypter = crypter
- vc.localID = localID.PublicID()
- vc.remoteID = remoteID
+ vc.localID = lID
+ vc.remoteID = rID
vc.mu.Unlock()
- vlog.VI(1).Infof("Client VC %v authenticated. RemoteID:%v LocalID:%v", vc, remoteID, localID)
+ vlog.VI(1).Infof("Client VC %v authenticated. RemoteID:%v LocalID:%v", vc, rID, lID)
return nil
}
@@ -430,12 +453,12 @@
result <- HandshakeResult{ln, err}
return result
}
- var localID security.PrivateID
+ var localID LocalID
var securityLevel veyron2.VCSecurityLevel
for _, o := range opts {
switch v := o.(type) {
- case ListenerIDOpt:
- localID = v.Identity()
+ case LocalID:
+ localID = v
case veyron2.VCSecurityLevel:
securityLevel = v
}
@@ -450,7 +473,9 @@
vc.helper.AddReceiveBuffers(vc.VCI(), SharedFlowID, DefaultBytesBufferedPerFlow)
switch securityLevel {
case veyron2.VCSecurityConfidential:
- localID = anonymousIfNilPrivateID(localID)
+ if localID == nil {
+ localID = FixedLocalID(anonymousID)
+ }
case veyron2.VCSecurityNone:
return finish(ln, nil)
default:
@@ -493,7 +518,7 @@
vc.mu.Lock()
vc.authFID = vc.findFlowLocked(authConn)
vc.mu.Unlock()
- remoteID, err := authenticateAsServer(authConn, localID, crypter)
+ rID, lID, err := authenticateAsServer(authConn, localID, crypter)
if err != nil {
sendErr(fmt.Errorf("Authentication failed: %v", err))
return
@@ -501,12 +526,12 @@
vc.mu.Lock()
vc.crypter = crypter
- vc.localID = localID.PublicID()
- vc.remoteID = remoteID
+ vc.localID = lID
+ vc.remoteID = rID
close(vc.acceptHandshakeDone)
vc.acceptHandshakeDone = nil
vc.mu.Unlock()
- vlog.VI(1).Infof("Server VC %v authenticated. RemoteID:%v LocalID:%v", vc, remoteID, localID)
+ vlog.VI(1).Infof("Server VC %v authenticated. RemoteID:%v LocalID:%v", vc, rID, lID)
result <- HandshakeResult{ln, nil}
}()
return result
@@ -641,15 +666,5 @@
if id != nil {
return id
}
- // TODO(ashankar): Have an Anonymous identity that also encodes the
- // public key so that changing the keys in code doesn't prevent new
- // binaries from talking to old ones.
- return security.FakePublicID("anonymous")
-}
-
-func anonymousIfNilPrivateID(id security.PrivateID) security.PrivateID {
- if id != nil {
- return id
- }
- return security.FakePrivateID("anonymous")
+ return anonymousID.PublicID()
}
diff --git a/runtimes/google/ipc/stream/vc/vc_test.go b/runtimes/google/ipc/stream/vc/vc_test.go
index ae93fd8..f1b1e2e 100644
--- a/runtimes/google/ipc/stream/vc/vc_test.go
+++ b/runtimes/google/ipc/stream/vc/vc_test.go
@@ -4,6 +4,7 @@
import (
"bytes"
+ "fmt"
"io"
"net"
"reflect"
@@ -18,6 +19,7 @@
"veyron/runtimes/google/lib/bqueue"
"veyron/runtimes/google/lib/bqueue/drrqueue"
"veyron/runtimes/google/lib/iobuf"
+ isecurity "veyron/runtimes/google/security"
"veyron2"
"veyron2/ipc/stream"
@@ -35,10 +37,18 @@
)
var (
- clientID = security.FakePrivateID("client")
- serverID = security.FakePrivateID("server")
+ clientID = newID("client")
+ serverID = newID("server")
)
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
// testFlowEcho writes a random string of 'size' bytes on the flow and then
// ensures that the same string is read back.
func testFlowEcho(t *testing.T, flow stream.Flow, size int) {
@@ -73,30 +83,43 @@
}
}
+func matchID(got, want security.PublicID) error {
+ if want == nil {
+ if got.Names() != nil {
+ return fmt.Errorf("got identity with names: %v, want one with names: nil", got.Names())
+ }
+ return nil
+ }
+ if g, w := got.Names(), want.Names(); !reflect.DeepEqual(got.Names(), want.Names()) {
+ return fmt.Errorf("got identity with names: %v, want one with names: %v", g, w)
+ }
+ if g, w := got.PublicKey(), want.PublicKey(); !reflect.DeepEqual(got.PublicKey(), want.PublicKey()) {
+ return fmt.Errorf("got identity with public key: %v, want one with public key: %v", g, w)
+ }
+ return nil
+}
+
func testHandshake(t *testing.T, security veyron2.VCSecurityLevel, localID, remoteID security.PublicID) {
h, vc := New(security)
flow, err := vc.Connect()
if err != nil {
t.Fatal(err)
}
- lID := flow.LocalID()
- if !reflect.DeepEqual(lID.Names(), localID.Names()) {
- t.Errorf("Client says LocalID is %q want %q", lID, localID)
+ lID, rID := flow.LocalID(), flow.RemoteID()
+ if (lID == nil) || (rID == nil) {
+ t.Error("Either the LocalID or the RemoteID of the flow is nil")
}
- rID := flow.RemoteID()
- if !reflect.DeepEqual(rID.Names(), remoteID.Names()) {
- t.Errorf("Client says RemoteID is %q want %q", rID, remoteID)
+ if err := matchID(lID, localID); err != nil {
+ t.Errorf("Client identity mismatch: %s", err)
}
- if g, w := lID.PublicKey(), localID.PublicKey(); !reflect.DeepEqual(g, w) {
- t.Errorf("Client identity public key mismatch. Got %v want %v", g, w)
- }
- if g, w := rID.PublicKey(), remoteID.PublicKey(); !reflect.DeepEqual(g, w) {
- t.Errorf("Server identity public key mismatch. Got %v want %v", g, w)
+ if err := matchID(rID, remoteID); err != nil {
+ t.Errorf("Server identity mismatch: %s", err)
}
h.Close()
}
+
func TestHandshake(t *testing.T) {
- testHandshake(t, SecurityNone, security.FakePublicID("anonymous"), security.FakePublicID("anonymous"))
+ testHandshake(t, SecurityNone, nil, nil)
}
func TestHandshakeTLS(t *testing.T) {
testHandshake(t, SecurityTLS, clientID.PublicID(), serverID.PublicID())
@@ -273,8 +296,8 @@
go clientH.pipeLoop(serverH.VC)
go serverH.pipeLoop(clientH.VC)
- c := serverH.VC.HandshakeAcceptedVC(security, vc.ListenerID(serverID))
- if err := clientH.VC.HandshakeDialedVC(security, veyron2.LocalID(clientID)); err != nil {
+ c := serverH.VC.HandshakeAcceptedVC(security, vc.FixedLocalID(serverID))
+ if err := clientH.VC.HandshakeDialedVC(security, vc.FixedLocalID(clientID)); err != nil {
panic(err)
}
hr := <-c
diff --git a/runtimes/google/ipc/stream/vif/vif_test.go b/runtimes/google/ipc/stream/vif/vif_test.go
index ab377f4..a743542 100644
--- a/runtimes/google/ipc/stream/vif/vif_test.go
+++ b/runtimes/google/ipc/stream/vif/vif_test.go
@@ -18,14 +18,21 @@
"veyron/runtimes/google/ipc/stream/vc"
"veyron/runtimes/google/ipc/stream/vif"
iversion "veyron/runtimes/google/ipc/version"
+ isecurity "veyron/runtimes/google/security"
- "veyron2"
"veyron2/ipc/stream"
"veyron2/ipc/version"
"veyron2/naming"
- "veyron2/security"
)
+func newLocalID(name string) vc.LocalID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return vc.FixedLocalID(id)
+}
+
func TestSingleFlowCreatedAtClient(t *testing.T) {
client, server := NewClientServer()
defer client.Close()
@@ -415,8 +422,7 @@
if client, err = vif.InternalNewDialedVIF(c1, naming.FixedRoutingID(0xc), clientVersions); err != nil {
panic(err)
}
- serverID := vc.ListenerID(security.FakePrivateID("server"))
- if server, err = vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), serverVersions, serverID); err != nil {
+ if server, err = vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), serverVersions, newLocalID("server")); err != nil {
panic(err)
}
return
@@ -456,8 +462,7 @@
scChan := make(chan stream.Connector)
errChan := make(chan error)
go func() {
- clientID := veyron2.LocalID(security.FakePrivateID("client"))
- vc, err := client.Dial(ep, clientID)
+ vc, err := client.Dial(ep, newLocalID("client"))
errChan <- err
vcChan <- vc
}()
diff --git a/runtimes/google/ipc/testutil_test.go b/runtimes/google/ipc/testutil_test.go
index e726013..f990ca7 100644
--- a/runtimes/google/ipc/testutil_test.go
+++ b/runtimes/google/ipc/testutil_test.go
@@ -5,7 +5,10 @@
"testing"
_ "veyron/lib/testutil"
+ "veyron/runtimes/google/ipc/stream/vc"
+ isecurity "veyron/runtimes/google/security"
+ "veyron2/ipc"
"veyron2/security"
"veyron2/verror"
)
@@ -38,19 +41,13 @@
}
}
-// listenerIDOpt implements vc.ListenerIDOpt and veyron2/ipc.ServerOpt.
-type listenerIDOpt struct {
- id security.PrivateID
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
}
-func (opt *listenerIDOpt) Identity() security.PrivateID {
- return opt.id
-}
-
-func (*listenerIDOpt) IPCStreamListenerOpt() {}
-
-func (*listenerIDOpt) IPCServerOpt() {}
-
-func listenerID(id security.PrivateID) *listenerIDOpt {
- return &listenerIDOpt{id}
-}
+var _ ipc.ClientOpt = vc.FixedLocalID(newID("irrelevant"))
+var _ ipc.ServerOpt = vc.FixedLocalID(newID("irrelevant"))
diff --git a/runtimes/google/jni/android.go b/runtimes/google/jni/android.go
new file mode 100644
index 0000000..d8ecb99
--- /dev/null
+++ b/runtimes/google/jni/android.go
@@ -0,0 +1,17 @@
+// +build android
+
+package main
+
+//TODO(bprosnitz) Move android code to a separate package so that we can make dependencies work
+
+import "syscall"
+
+// #cgo LDFLAGS: -ljniwrapper
+// #include "jni_wrapper.h"
+// #include <stdlib.h>
+import "C"
+
+//export Java_com_veyron_runtimes_google_android_RedirectStderr_nativeStart
+func Java_com_veyron_runtimes_google_android_RedirectStderr_nativeStart(env *C.JNIEnv, jRuntime C.jclass, fileno C.jint) {
+ syscall.Dup2(int(fileno), syscall.Stderr)
+}
diff --git a/runtimes/google/naming/namespace/all_test.go b/runtimes/google/naming/namespace/all_test.go
index e921c39..64d6743 100644
--- a/runtimes/google/naming/namespace/all_test.go
+++ b/runtimes/google/naming/namespace/all_test.go
@@ -36,7 +36,7 @@
}
}
-func doGlob(t *testing.T, ctx context.T, ns naming.Namespace, pattern string) []string {
+func doGlob(t *testing.T, ctx context.T, ns naming.Namespace, pattern string, limit int) []string {
var replies []string
rc, err := ns.Glob(ctx, pattern)
if err != nil {
@@ -44,6 +44,9 @@
}
for s := range rc {
replies = append(replies, s.Name)
+ if limit > 0 && len(replies) > limit {
+ boom(t, "Glob returns too many results, perhaps not limiting recursion")
+ }
}
return replies
}
@@ -381,10 +384,10 @@
{"*/f??/*z", []string{"mt4/foo/baz"}},
}
for _, test := range globTests {
- out := doGlob(t, r, ns, test.pattern)
+ out := doGlob(t, r, ns, test.pattern, 0)
compare(t, "Glob", test.pattern, test.expected, out)
// Do the same with a full rooted name.
- out = doGlob(t, r, ns, naming.JoinAddressName(root.name, test.pattern))
+ out = doGlob(t, r, ns, naming.JoinAddressName(root.name, test.pattern), 0)
var expectedWithRoot []string
for _, s := range test.expected {
expectedWithRoot = append(expectedWithRoot, naming.JoinAddressName(root.name, s))
@@ -394,8 +397,6 @@
}
func TestCycles(t *testing.T) {
- t.Skip() // Remove when the bug is fixed.
-
sr := rt.Init()
r, _ := rt.New() // We use a different runtime for the client side.
defer r.Cleanup()
@@ -440,19 +441,8 @@
boom(t, "Failed to detect cycle")
}
- // Remove the timeout when the bug is fixed, right now, this just
- // finishes the test immediately. Add a comparison for the expected
- // output from glob also.
- ch := make(chan struct{})
- go func() {
- doGlob(t, r, ns, "c1/...")
- close(ch)
- }()
- select {
- case <-ch:
- case <-time.After(time.Millisecond * 100):
- t.Errorf("glob timedout")
- }
+ // Perform the glob with a response length limit.
+ doGlob(t, r, ns, "c1/...", 1000)
}
func TestUnresolve(t *testing.T) {
@@ -469,7 +459,7 @@
ns := r.Namespace()
ns.SetRoots(root.name)
- vlog.Infof("Glob: %v", doGlob(t, r, ns, "*"))
+ vlog.Infof("Glob: %v", doGlob(t, r, ns, "*", 0))
testResolve(t, r, ns, "joke1", jokes["joke1"].name)
testUnresolve(t, r, ns, "joke1", "")
}
diff --git a/runtimes/google/naming/namespace/glob.go b/runtimes/google/naming/namespace/glob.go
index 2560bfb..68d491f 100644
--- a/runtimes/google/naming/namespace/glob.go
+++ b/runtimes/google/naming/namespace/glob.go
@@ -15,6 +15,11 @@
const mountTableGlobReplyStreamLength = 100
+type queuedEntry struct {
+ me *naming.MountEntry
+ depth int // number of mount tables traversed recursively
+}
+
// globAtServer performs a Glob at a single server and adds any results to the list. Paramters are:
// server the server to perform the glob at. This may include multiple names for different
// instances of the same server.
@@ -22,7 +27,8 @@
// l the list to add results to.
// recursive true to continue below the matched pattern
// We return a bool foundRoot which indicates whether the empty name "" was found on a target server.
-func (ns *namespace) globAtServer(ctx context.T, server *naming.MountEntry, pattern *glob.Glob, l *list.List) (bool, error) {
+func (ns *namespace) globAtServer(ctx context.T, qe *queuedEntry, pattern *glob.Glob, l *list.List) (bool, error) {
+ server := qe.me
pstr := pattern.String()
foundRoot := false
vlog.VI(2).Infof("globAtServer(%v, %v)", *server, pstr)
@@ -62,10 +68,21 @@
// Convert to the ever so slightly different name.MountTable version of a MountEntry
// and add it to the list.
- l.PushBack(&naming.MountEntry{
- Name: e.Name,
- Servers: convertServers(e.Servers),
- })
+ x := &queuedEntry{
+ me: &naming.MountEntry{
+ Name: e.Name,
+ Servers: convertServers(e.Servers),
+ },
+ depth: qe.depth,
+ }
+ // x.depth is the number of severs we've walked through since we've gone
+ // recursive (i.e. with pattern length of 0).
+ if pattern.Len() == 0 {
+ if x.depth++; x.depth > ns.maxRecursiveGlobDepth {
+ continue
+ }
+ }
+ l.PushBack(x)
}
if err := call.Finish(); err != nil {
@@ -135,16 +152,20 @@
func (ns *namespace) globLoop(ctx context.T, servers []string, prefix string, pattern *glob.Glob, reply chan naming.MountEntry) {
defer close(reply)
+ // As we encounter new mount tables while traversing the Glob, we add them to the list 'l'. The loop below
+ // traverses this list removing a mount table each time and calling globAtServer to perform a glob at that
+ // server. globAtServer will send on 'reply' any terminal entries that match the glob and add any new mount
+ // tables to be traversed to the list 'l'.
l := list.New()
- l.PushBack(&naming.MountEntry{Name: "", Servers: convertStringsToServers(servers)})
+ l.PushBack(&queuedEntry{me: &naming.MountEntry{Name: "", Servers: convertStringsToServers(servers)}})
// Perform a breadth first search of the name graph.
for le := l.Front(); le != nil; le = l.Front() {
l.Remove(le)
- e := le.Value.(*naming.MountEntry)
+ e := le.Value.(*queuedEntry)
// Get the pattern elements below the current path.
- suffix := pattern.Split(depth(e.Name))
+ suffix := pattern.Split(depth(e.me.Name))
// Perform a glob at the server.
foundRoot, err := ns.globAtServer(ctx, e, suffix, l)
@@ -152,7 +173,7 @@
// We want to output this entry if:
// 1. There was a real error, we return whatever name gave us the error.
if err != nil && !notAnMT(err) {
- x := *e
+ x := *e.me
x.Name = naming.Join(prefix, x.Name)
x.Error = err
reply <- x
@@ -161,7 +182,7 @@
// 2. The current name fullfills the pattern and further servers did not respond
// with "". That is, we want to prefer foo/ over foo.
if suffix.Len() == 0 && !foundRoot {
- x := *e
+ x := *e.me
x.Name = naming.Join(prefix, x.Name)
reply <- x
}
diff --git a/runtimes/google/naming/namespace/namespace.go b/runtimes/google/naming/namespace/namespace.go
index 16b4fdd..65e0eb5 100644
--- a/runtimes/google/naming/namespace/namespace.go
+++ b/runtimes/google/naming/namespace/namespace.go
@@ -9,6 +9,9 @@
"veyron2/verror"
)
+const defaultMaxResolveDepth = 32
+const defaultMaxRecursiveGlobDepth = 10
+
// namespace is an implementation of naming.MountTable.
type namespace struct {
sync.RWMutex
@@ -16,6 +19,10 @@
// the default root servers for resolutions in this namespace.
roots []string
+
+ // depth limits
+ maxResolveDepth int
+ maxRecursiveGlobDepth int
}
func rooted(names []string) bool {
@@ -37,7 +44,12 @@
return nil, badRoots(roots)
}
// A namespace with no roots can still be used for lookups of rooted names.
- return &namespace{rt: rt, roots: roots}, nil
+ return &namespace{
+ rt: rt,
+ roots: roots,
+ maxResolveDepth: defaultMaxResolveDepth,
+ maxRecursiveGlobDepth: defaultMaxRecursiveGlobDepth,
+ }, nil
}
// SetRoots implements naming.MountTable.SetRoots
@@ -52,6 +64,16 @@
return nil
}
+// SetDepthLimits overrides the default limits.
+func (ns *namespace) SetDepthLimits(resolve, glob int) {
+ if resolve >= 0 {
+ ns.maxResolveDepth = resolve
+ }
+ if glob >= 0 {
+ ns.maxRecursiveGlobDepth = glob
+ }
+}
+
// Roots implements naming.MountTable.Roots
func (ns *namespace) Roots() []string {
ns.RLock()
diff --git a/runtimes/google/naming/namespace/resolve.go b/runtimes/google/naming/namespace/resolve.go
index 3043a31..4e1fdf4 100644
--- a/runtimes/google/naming/namespace/resolve.go
+++ b/runtimes/google/naming/namespace/resolve.go
@@ -11,8 +11,6 @@
"veyron2/vlog"
)
-const maxDepth = 32
-
func convertServersToStrings(servers []mountedServer, suffix string) (ret []string) {
for _, s := range servers {
ret = append(ret, naming.Join(s.Server, suffix))
@@ -85,7 +83,7 @@
return nil, naming.ErrNoMountTable
}
// Iterate walking through mount table servers.
- for remaining := maxDepth; remaining > 0; remaining-- {
+ for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("Resolve(%s) loop %s", name, names)
if terminal(names) {
vlog.VI(1).Infof("Resolve(%s) -> %s", name, names)
@@ -125,7 +123,7 @@
return nil, naming.ErrNoMountTable
}
last := names
- for remaining := maxDepth; remaining > 0; remaining-- {
+ for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("ResolveToMountTable(%s) loop %s", name, names)
var err error
curr := names
@@ -212,7 +210,7 @@
if err != nil {
return nil, err
}
- for remaining := maxDepth; remaining > 0; remaining-- {
+ for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("Unresolve loop %s", names)
curr := names
if names, err = unresolveAgainstServer(ctx, ns.rt.Client(), names); err != nil {
diff --git a/runtimes/google/rt/ipc.go b/runtimes/google/rt/ipc.go
index 2b54fad..150bfdf 100644
--- a/runtimes/google/rt/ipc.go
+++ b/runtimes/google/rt/ipc.go
@@ -1,22 +1,81 @@
package rt
import (
+ "errors"
"fmt"
iipc "veyron/runtimes/google/ipc"
imanager "veyron/runtimes/google/ipc/stream/manager"
+ "veyron/runtimes/google/ipc/stream/vc"
"veyron2"
"veyron2/context"
"veyron2/ipc"
"veyron2/ipc/stream"
"veyron2/naming"
+ "veyron2/security"
)
+// fixedPublicIDStore implements security.PublicIDStore. It embeds a (fixed) PublicID that
+// is both the default and the PublicID to be used for any peer. Adding a new PublicID
+// to the store is disallowed, and setting the default principal-pattern is a no-op.
+type fixedPublicIDStore struct {
+ id security.PublicID
+}
+
+func (fixedPublicIDStore) Add(id security.PublicID, peerPattern security.PrincipalPattern) error {
+ return errors.New("adding new PublicIDs is disallowed for this PublicIDStore")
+}
+
+func (s fixedPublicIDStore) ForPeer(peer security.PublicID) (security.PublicID, error) {
+ return s.id, nil
+}
+
+func (s fixedPublicIDStore) DefaultPublicID() (security.PublicID, error) {
+ return s.id, nil
+}
+
+func (fixedPublicIDStore) SetDefaultPrincipalPattern(pattern security.PrincipalPattern) {}
+
+// localID is an option for passing a PrivateID and PublicIDStore
+// to a server or client.
+type localID struct {
+ id security.PrivateID
+ store security.PublicIDStore
+}
+
+func (lID *localID) Sign(message []byte) (security.Signature, error) {
+ return lID.id.Sign(message)
+}
+
+func (lID *localID) AsClient(server security.PublicID) (security.PublicID, error) {
+ return lID.store.ForPeer(server)
+}
+
+func (lID *localID) AsServer() (security.PublicID, error) {
+ return lID.store.DefaultPublicID()
+}
+
+func (*localID) IPCClientOpt() {}
+func (*localID) IPCStreamVCOpt() {}
+func (*localID) IPCServerOpt() {}
+func (*localID) IPCStreamListenerOpt() {}
+
+// newLocalID returns a localID embedding the runtime's PrivateID and a fixed
+// PublicIDStore constructed from the provided PublicID or the runtiume's PublicIDStore
+// if the provided PublicID is nil.
+func (rt *vrt) newLocalID(id security.PublicID) vc.LocalID {
+ lID := &localID{id: rt.id, store: rt.store}
+ if id != nil {
+ lID.store = fixedPublicIDStore{id}
+ }
+ return lID
+}
+
func (rt *vrt) NewClient(opts ...ipc.ClientOpt) (ipc.Client, error) {
sm := rt.sm
ns := rt.ns
- cIDOpt := veyron2.LocalID(rt.id.Identity())
+ var id security.PublicID
var otherOpts []ipc.ClientOpt
for _, opt := range opts {
switch topt := opt.(type) {
@@ -25,14 +84,14 @@
case veyron2.NamespaceOpt:
ns = topt.Namespace
case veyron2.LocalIDOpt:
- cIDOpt = topt
+ id = topt.PublicID
default:
otherOpts = append(otherOpts, opt)
}
}
- if cIDOpt.PrivateID != nil {
- otherOpts = append(otherOpts, cIDOpt)
- }
+ // Add the option that provides the local identity to the client.
+ otherOpts = append(otherOpts, rt.newLocalID(id))
+
return iipc.InternalNewClient(sm, ns, otherOpts...)
}
@@ -68,17 +127,20 @@
// Start the http debug server exactly once for this runtime.
rt.startHTTPDebugServerOnce()
ns := rt.ns
+ var id security.PublicID
var otherOpts []ipc.ServerOpt
for _, opt := range opts {
switch topt := opt.(type) {
case veyron2.NamespaceOpt:
ns = topt
+ case veyron2.LocalIDOpt:
+ id = topt.PublicID
default:
otherOpts = append(otherOpts, opt)
}
}
- // Add the option that provides the identity currently used by the runtime.
- otherOpts = append(otherOpts, rt.id)
+ // Add the option that provides the local identity to the server.
+ otherOpts = append(otherOpts, rt.newLocalID(id))
ctx := rt.NewContext()
return iipc.InternalNewServer(ctx, sm, ns, otherOpts...)
diff --git a/runtimes/google/rt/ipc_test.go b/runtimes/google/rt/ipc_test.go
new file mode 100644
index 0000000..1822541
--- /dev/null
+++ b/runtimes/google/rt/ipc_test.go
@@ -0,0 +1,188 @@
+package rt_test
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+ "testing"
+ "time"
+
+ _ "veyron/lib/testutil"
+ isecurity "veyron/runtimes/google/security"
+
+ "veyron2"
+ "veyron2/ipc"
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/security"
+)
+
+type testService struct{}
+
+func (*testService) EchoIDs(call ipc.ServerCall) (server, client []string) {
+ return call.LocalID().Names(), call.RemoteID().Names()
+}
+
+type S []string
+
+func newID(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+func bless(blessor security.PrivateID, blessee security.PublicID, name string) security.PublicID {
+ blessedID, err := blessor.Bless(blessee, name, 5*time.Minute, nil)
+ if err != nil {
+ panic(err)
+ }
+ return blessedID
+}
+
+func add(store security.PublicIDStore, id security.PublicID, pattern security.PrincipalPattern) {
+ if err := store.Add(id, pattern); err != nil {
+ panic(err)
+ }
+}
+
+func call(r veyron2.Runtime, client ipc.Client, name string) (clientNames, serverNames []string, err error) {
+ c, err := client.StartCall(r.NewContext(), name, "EchoIDs", nil)
+ if err != nil {
+ return nil, nil, err
+ }
+ if err := c.Finish(&serverNames, &clientNames); err != nil {
+ return nil, nil, err
+ }
+ sort.Strings(clientNames)
+ sort.Strings(serverNames)
+ return
+}
+
+func TestClientServerIDs(t *testing.T) {
+ stopServer := func(server ipc.Server) {
+ if err := server.Stop(); err != nil {
+ t.Fatalf("server.Stop failed: %s", err)
+ }
+ }
+ var (
+ self = newID("self")
+ google = newID("google")
+ veyron = newID("veyron")
+
+ googleGmailService = bless(google, self.PublicID(), "gmail")
+ googleYoutubeService = bless(google, self.PublicID(), "youtube")
+ veyronService = bless(veyron, self.PublicID(), "service")
+ googleGmailClient = bless(google, self.PublicID(), "gmailClient")
+ googleYoutubeClient = bless(google, self.PublicID(), "youtubeClient")
+ veyronClient = bless(veyron, self.PublicID(), "client")
+ )
+ isecurity.TrustIdentityProviders(google)
+ isecurity.TrustIdentityProviders(veyron)
+
+ serverR, err := rt.New(veyron2.RuntimeID(self))
+ if err != nil {
+ t.Fatalf("rt.New() failed: %s", err)
+ }
+ clientR, err := rt.New(veyron2.RuntimeID(self))
+ if err != nil {
+ t.Fatalf("rt.New() failed: %s", err)
+ }
+
+ // Add PublicIDs for running "google/gmail" and "google/youtube" services to
+ // serverR's PublicIDStore. Since these PublicIDs are meant to be by
+ // servers only they are tagged with "".
+ add(serverR.PublicIDStore(), googleGmailService, "")
+ add(serverR.PublicIDStore(), googleYoutubeService, "")
+ // Add PublicIDs for communicating the "google/gmail" and "google/youtube" services
+ // to the clientR's PublicIDStore.
+ add(clientR.PublicIDStore(), googleGmailClient, "google/*")
+ add(clientR.PublicIDStore(), googleYoutubeClient, "google/youtube")
+
+ type testcase struct {
+ server, client security.PublicID
+ defaultPattern security.PrincipalPattern
+ wantServerNames, wantClientNames []string
+ }
+ tests := []testcase{
+ {
+ defaultPattern: security.AllPrincipals,
+ wantServerNames: S{"self", "google/gmail", "google/youtube"},
+ wantClientNames: S{"self", "google/gmailClient", "google/youtubeClient"},
+ },
+ {
+ defaultPattern: "google/gmail",
+ wantServerNames: S{"google/gmail"},
+ wantClientNames: S{"self", "google/gmailClient"},
+ },
+ {
+ defaultPattern: "google/youtube",
+ wantServerNames: S{"google/youtube"},
+ wantClientNames: S{"self", "google/gmailClient", "google/youtubeClient"},
+ },
+ {
+ server: veyronService,
+ defaultPattern: security.AllPrincipals,
+ wantServerNames: S{"veyron/service"},
+ wantClientNames: S{"self"},
+ },
+ {
+ client: veyronClient,
+ defaultPattern: security.AllPrincipals,
+ wantServerNames: S{"self", "google/gmail", "google/youtube"},
+ wantClientNames: S{"veyron/client"},
+ },
+ {
+ server: veyronService,
+ client: veyronClient,
+ defaultPattern: security.AllPrincipals,
+ wantServerNames: S{"veyron/service"},
+ wantClientNames: S{"veyron/client"},
+ },
+ }
+ name := func(t testcase) string {
+ return fmt.Sprintf("TestCase{clientPublicIDStore: %v, serverPublicIDStore: %v, client option: %v, server option: %v}", clientR.PublicIDStore(), serverR.PublicIDStore(), t.client, t.server)
+ }
+ for _, test := range tests {
+ serverR.PublicIDStore().SetDefaultPrincipalPattern(test.defaultPattern)
+ server, err := serverR.NewServer(veyron2.LocalID(test.server))
+ if err != nil {
+ t.Errorf("serverR.NewServer(...) failed: %s", err)
+ continue
+ }
+ endpoint, err := server.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Errorf("error listening to service: ", err)
+ continue
+ }
+ defer stopServer(server)
+ if err := server.Serve("", ipc.SoloDispatcher(&testService{}, security.NewACLAuthorizer(security.ACL{security.AllPrincipals: security.AllLabels}))); err != nil {
+ t.Errorf("error serving service: ", err)
+ continue
+ }
+
+ client, err := clientR.NewClient(veyron2.LocalID(test.client))
+ if err != nil {
+ t.Errorf("clientR.NewClient(...) failed: %s", err)
+ continue
+ }
+ defer client.Close()
+
+ clientNames, serverNames, err := call(clientR, client, naming.JoinAddressName(fmt.Sprintf("%v", endpoint), ""))
+ if err != nil {
+ t.Errorf("IPC failed: %s", err)
+ continue
+ }
+ sort.Strings(test.wantClientNames)
+ sort.Strings(test.wantServerNames)
+ if !reflect.DeepEqual(clientNames, test.wantClientNames) {
+ t.Errorf("TestCase: %s, Got clientNames: %v, want: %v", name(test), clientNames, test.wantClientNames)
+ continue
+ }
+ if !reflect.DeepEqual(serverNames, test.wantServerNames) {
+ t.Errorf("TestCase: %s, Got serverNames: %v, want: %v", name(test), serverNames, test.wantServerNames)
+ continue
+ }
+ }
+}
diff --git a/runtimes/google/rt/mgmt.go b/runtimes/google/rt/mgmt.go
index ecb62f1..e476ec0 100644
--- a/runtimes/google/rt/mgmt.go
+++ b/runtimes/google/rt/mgmt.go
@@ -7,7 +7,6 @@
"time"
"veyron/runtimes/google/appcycle"
- vflag "veyron/security/flag"
"veyron/services/mgmt/lib/exec"
"veyron2"
@@ -57,7 +56,7 @@
if ep, err = m.server.Listen("tcp", "127.0.0.1:0"); err != nil {
return err
}
- if err := m.server.Serve("", ipc.SoloDispatcher(appcycle.NewServerAppCycle(m), vflag.NewAuthorizerOrDie())); err != nil {
+ if err := m.server.Serve("", ipc.SoloDispatcher(appcycle.NewServerAppCycle(m), nil)); err != nil {
return err
}
return m.callbackToParent(parentName, naming.JoinAddressName(ep.String(), ""))
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index 805adf4..3f4f429 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -15,6 +15,7 @@
"veyron2/ipc/stream"
"veyron2/naming"
"veyron2/product"
+ "veyron2/security"
"veyron2/vlog"
)
@@ -23,7 +24,8 @@
sm stream.Manager
ns naming.Namespace
signals chan os.Signal
- id *currentIDOpt
+ id security.PrivateID
+ store security.PublicIDStore
client ipc.Client
mgmt *mgmtImpl
debug debugServer
@@ -61,13 +63,13 @@
func (rt *vrt) init(opts ...veyron2.ROpt) error {
flag.Parse()
rt.initHTTPDebugServer()
- rt.id = ¤tIDOpt{}
nsRoots := []string{}
-
for _, o := range opts {
switch v := o.(type) {
- case veyron2.LocalIDOpt:
- rt.id.setIdentity(v.PrivateID)
+ case veyron2.RuntimeIDOpt:
+ rt.id = v.PrivateID
+ case veyron2.RuntimePublicIDStoreOpt:
+ rt.store = v
case veyron2.ProductOpt:
rt.product = v.T
case veyron2.NamespaceRoots:
@@ -123,11 +125,11 @@
return err
}
- if err = rt.initIdentity(); err != nil {
+ if err = rt.initSecurity(); err != nil {
return err
}
- if rt.client, err = rt.NewClient(veyron2.LocalID(rt.id.Identity())); err != nil {
+ if rt.client, err = rt.NewClient(); err != nil {
return err
}
diff --git a/runtimes/google/rt/security.go b/runtimes/google/rt/security.go
index b25b95d..b14babf 100644
--- a/runtimes/google/rt/security.go
+++ b/runtimes/google/rt/security.go
@@ -4,7 +4,6 @@
"fmt"
"os"
"os/user"
- "sync"
isecurity "veyron/runtimes/google/security"
@@ -12,61 +11,53 @@
"veyron2/vlog"
)
-// currentIDOpt is an option that can be used to pass the identity currently used
-// by the runtime to an ipc.Server or ipc.StreamListener.
-type currentIDOpt struct {
- id security.PrivateID
- mu sync.RWMutex
-}
-
-func (id *currentIDOpt) Identity() security.PrivateID {
- id.mu.RLock()
- defer id.mu.RUnlock()
- return id.id
-}
-
-func (id *currentIDOpt) setIdentity(newID security.PrivateID) {
- // TODO(ataly): Whenever setIdentity is invoked on the identity currently used by
- // the runtime, the following changes must also be performed:
- // * the identity provider of the new identity must be tursted.
- // * the default client used by the runtime must also be replaced with
- // a client using the new identity.
- id.mu.Lock()
- defer id.mu.Unlock()
- id.id = newID
-}
-
-func (*currentIDOpt) IPCServerOpt() {}
-
-func (*currentIDOpt) IPCStreamListenerOpt() {}
-
func (rt *vrt) NewIdentity(name string) (security.PrivateID, error) {
return isecurity.NewPrivateID(name)
}
func (rt *vrt) Identity() security.PrivateID {
- return rt.id.Identity()
+ return rt.id
+}
+
+func (rt *vrt) PublicIDStore() security.PublicIDStore {
+ return rt.store
+}
+
+func (rt *vrt) initSecurity() error {
+ if err := rt.initIdentity(); err != nil {
+ return err
+ }
+ if rt.store == nil {
+ rt.store = isecurity.NewPublicIDStore()
+ // TODO(ashankar,ataly): What should the tag for the runtime's PublicID in the
+ // runtime's store be? Below we use security.AllPrincipals but this means that
+ // the PublicID *always* gets used for any peer. This may not be desirable.
+ if err := rt.store.Add(rt.id.PublicID(), security.AllPrincipals); err != nil {
+ return fmt.Errorf("could not initialize a PublicIDStore for the runtime: %s", err)
+ }
+ }
+ // Always trust our own identity providers.
+ // TODO(ataly, ashankar): We should trust the identity providers of all PublicIDs in the store.
+ trustIdentityProviders(rt.id)
+ return nil
}
func (rt *vrt) initIdentity() error {
- if rt.id.Identity() == nil {
- var id security.PrivateID
- var err error
- if file := os.Getenv("VEYRON_IDENTITY"); len(file) > 0 {
- if id, err = loadIdentityFromFile(file); err != nil {
- return fmt.Errorf("Could not load identity from %q: %v", file, err)
- }
- } else {
- name := defaultIdentityName()
- vlog.VI(2).Infof("No identity provided to the runtime, minting one for %q", name)
- if id, err = rt.NewIdentity(name); err != nil {
- return fmt.Errorf("Could not create new identity: %v", err)
- }
- }
- rt.id.setIdentity(id)
+ if rt.id != nil {
+ return nil
}
- // Always trust our own identity providers.
- trustIdentityProviders(rt.id.Identity())
+ var err error
+ if file := os.Getenv("VEYRON_IDENTITY"); len(file) > 0 {
+ if rt.id, err = loadIdentityFromFile(file); err != nil || rt.id == nil {
+ return fmt.Errorf("Could not load identity from %q: %v", file, err)
+ }
+ } else {
+ name := defaultIdentityName()
+ vlog.VI(2).Infof("No identity provided to the runtime, minting one for %q", name)
+ if rt.id, err = rt.NewIdentity(name); err != nil || rt.id == nil {
+ return fmt.Errorf("Could not create new identity: %v", err)
+ }
+ }
return nil
}
diff --git a/runtimes/google/vsync/vsync.vdl.go b/runtimes/google/vsync/vsync.vdl.go
index f81a4b7..3b3481f 100644
--- a/runtimes/google/vsync/vsync.vdl.go
+++ b/runtimes/google/vsync/vsync.vdl.go
@@ -108,14 +108,26 @@
type SyncGetDeltasStream interface {
// Recv returns the next item in the input stream, blocking until
- // an item is available. Returns io.EOF to indicate graceful end of input.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item LogRec, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish blocks until the server is done and returns the positional
+ // return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (reply GenVector, err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -144,7 +156,7 @@
// GetDeltas in the service interface Sync.
type SyncServiceGetDeltasStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item LogRec) error
}
diff --git a/services/mgmt/build/constants.go b/services/mgmt/build/constants.go
deleted file mode 100644
index d150780..0000000
--- a/services/mgmt/build/constants.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package build
-
-type OperatingSystem uint8
-
-const (
- LINUX OperatingSystem = iota
- DARWIN
- WINDOWS
-)
-
-func (os OperatingSystem) String() string {
- switch os {
- case LINUX:
- return "linux"
- case DARWIN:
- return "darwin"
- case WINDOWS:
- return "windows"
- default:
- return "unknown"
- }
-}
-
-type Format uint8
-
-const (
- ELF Format = iota
- MACH
- PE
-)
-
-func (format Format) String() string {
- switch format {
- case ELF:
- return "elf"
- case MACH:
- return "mach-o"
- case PE:
- return "pe"
- default:
- return "unknown"
- }
-}
-
-type Architecture uint8
-
-const (
- AMD64 Architecture = iota
- ARM
- X86
-)
-
-func (arch Architecture) String() string {
- switch arch {
- case AMD64:
- return "amd64"
- case ARM:
- return "arm"
- case X86:
- return "x86"
- default:
- return "unknown"
- }
-}
diff --git a/services/mgmt/build/impl/impl_test.go b/services/mgmt/build/impl/impl_test.go
index 31b2d71..0404e7b 100644
--- a/services/mgmt/build/impl/impl_test.go
+++ b/services/mgmt/build/impl/impl_test.go
@@ -1,6 +1,7 @@
package impl
import (
+ "io"
"os"
"path/filepath"
"strings"
@@ -49,31 +50,43 @@
}
}
-func invokeBuild(t *testing.T, client build.Build, files []build.File) ([]byte, error) {
+func invokeBuild(t *testing.T, client build.Build, files []build.File) ([]byte, []build.File, error) {
stream, err := client.Build(rt.R().NewContext())
if err != nil {
t.Errorf("Build() failed: %v", err)
- return nil, err
+ return nil, nil, err
}
for _, file := range files {
if err := stream.Send(file); err != nil {
t.Logf("Send() failed: %v", err)
stream.Cancel()
- return nil, err
+ return nil, nil, err
}
}
if err := stream.CloseSend(); err != nil {
t.Logf("CloseSend() failed: %v", err)
stream.Cancel()
- return nil, err
+ return nil, nil, err
+ }
+ bins := make([]build.File, 0)
+ for {
+ bin, err := stream.Recv()
+ if err != nil && err != io.EOF {
+ t.Logf("Recv() failed: %v", err)
+ return nil, nil, err
+ }
+ if err == io.EOF {
+ break
+ }
+ bins = append(bins, bin)
}
output, err := stream.Finish()
if err != nil {
t.Logf("Finish() failed: %v", err)
stream.Cancel()
- return nil, err
+ return nil, nil, err
}
- return output, nil
+ return output, bins, nil
}
const mainSrc = `package main
@@ -97,13 +110,16 @@
Contents: []byte(mainSrc),
},
}
- output, err := invokeBuild(t, client, files)
+ output, bins, err := invokeBuild(t, client, files)
if err != nil {
t.FailNow()
}
if got, expected := strings.TrimSpace(string(output)), "test"; got != expected {
t.Fatalf("Unexpected output: got %v, expected %v", got, expected)
}
+ if got, expected := len(bins), 1; got != expected {
+ t.Fatalf("Unexpected number of binaries: got %v, expected %v", got, expected)
+ }
}
// TestFailure checks that the build server fails to build a package
@@ -118,7 +134,7 @@
Contents: []byte(""),
},
}
- if _, err := invokeBuild(t, client, files); err == nil {
+ if _, _, err := invokeBuild(t, client, files); err == nil {
t.FailNow()
}
}
diff --git a/services/mgmt/build/impl/invoker.go b/services/mgmt/build/impl/invoker.go
index b2b9c52..2ef2585 100644
--- a/services/mgmt/build/impl/invoker.go
+++ b/services/mgmt/build/impl/invoker.go
@@ -1,6 +1,7 @@
package impl
import (
+ "bytes"
"errors"
"io"
"io/ioutil"
@@ -15,7 +16,8 @@
)
var (
- errOperationFailed = errors.New("operation failed")
+ errBuildFailed = errors.New("build failed")
+ errInternalError = errors.New("internal error")
)
// invoker holds the state of a build server invocation.
@@ -33,25 +35,28 @@
// BUILD INTERFACE IMPLEMENTATION
+// TODO(jsimsa): Add support for building for a specific profile
+// specified as a suffix the Build().
func (i *invoker) Build(_ ipc.ServerContext, stream build.BuildServiceBuildStream) ([]byte, error) {
+ vlog.VI(1).Infof("Build() called.")
dir, prefix := "", ""
dirPerm, filePerm := os.FileMode(0700), os.FileMode(0600)
root, err := ioutil.TempDir(dir, prefix)
if err != nil {
vlog.Errorf("TempDir(%v, %v) failed: %v", dir, prefix, err)
- return nil, errOperationFailed
+ return nil, errInternalError
}
defer os.RemoveAll(root)
srcDir := filepath.Join(root, "go", "src")
if err := os.MkdirAll(srcDir, dirPerm); err != nil {
vlog.Errorf("MkdirAll(%v, %v) failed: %v", srcDir, dirPerm, err)
- return nil, errOperationFailed
+ return nil, errInternalError
}
for {
srcFile, err := stream.Recv()
if err != nil && err != io.EOF {
vlog.Errorf("Recv() failed: %v", err)
- return nil, errOperationFailed
+ return nil, errInternalError
}
if err == io.EOF {
break
@@ -60,21 +65,50 @@
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, dirPerm); err != nil {
vlog.Errorf("MkdirAll(%v, %v) failed: %v", dir, dirPerm, err)
- return nil, errOperationFailed
+ return nil, errInternalError
}
if err := ioutil.WriteFile(filePath, srcFile.Contents, filePerm); err != nil {
vlog.Errorf("WriteFile(%v, %v) failed: %v", filePath, filePerm, err)
- return nil, errOperationFailed
+ return nil, errInternalError
}
}
- cmd := exec.Command(i.gobin, "build", "-v", "...")
+ cmd := exec.Command(i.gobin, "install", "-v", "...")
cmd.Env = append(cmd.Env, "GOPATH="+filepath.Dir(srcDir))
- bytes, err := cmd.CombinedOutput()
- if err != nil {
- vlog.Errorf("CombinedOutput() failed: %v", err)
- return nil, errOperationFailed
+ var output bytes.Buffer
+ cmd.Stdout = &output
+ cmd.Stderr = &output
+ if err := cmd.Run(); err != nil {
+ vlog.Errorf("Run() failed: %v", err)
+ if output.Len() != 0 {
+ vlog.Errorf("%v", output.String())
+ }
+ return output.Bytes(), errBuildFailed
}
- return bytes, nil
+ binDir := filepath.Join(root, "go", "bin")
+ files, err := ioutil.ReadDir(binDir)
+ if err != nil {
+ vlog.Errorf("ReadDir(%v) failed: %v", binDir, err)
+ return nil, errInternalError
+ }
+ // TODO(jsimsa): Analyze the binary files for non-standard shared
+ // library dependencies.
+ for _, file := range files {
+ binPath := filepath.Join(root, "go", "bin", file.Name())
+ bytes, err := ioutil.ReadFile(binPath)
+ if err != nil {
+ vlog.Errorf("ReadFile(%v) failed: %v", binPath, err)
+ return nil, errInternalError
+ }
+ result := build.File{
+ Name: "bin/" + file.Name(),
+ Contents: bytes,
+ }
+ if err := stream.Send(result); err != nil {
+ vlog.Errorf("Send() failed: %v", err)
+ return nil, errInternalError
+ }
+ }
+ return output.Bytes(), nil
}
func (i *invoker) Describe(_ ipc.ServerContext, name string) (binary.Description, error) {
diff --git a/services/mgmt/build/impl/util.go b/services/mgmt/build/impl/util.go
new file mode 100644
index 0000000..2710ae9
--- /dev/null
+++ b/services/mgmt/build/impl/util.go
@@ -0,0 +1,59 @@
+package impl
+
+import (
+ "runtime"
+
+ "veyron2/services/mgmt/build"
+)
+
+func getArch() build.Architecture {
+ switch runtime.GOARCH {
+ case "386":
+ return build.X86
+ case "amd64":
+ return build.AMD64
+ case "arm":
+ return build.ARM
+ default:
+ return build.UnsupportedArchitecture
+ }
+}
+
+func getOS() build.OperatingSystem {
+ switch runtime.GOOS {
+ case "darwin":
+ return build.Darwin
+ case "linux":
+ return build.Linux
+ case "windows":
+ return build.Windows
+ default:
+ return build.UnsupportedOperatingSystem
+ }
+}
+
+func archString(arch build.Architecture) string {
+ switch arch {
+ case build.X86:
+ return "x86"
+ case build.AMD64:
+ return "amd64"
+ case build.ARM:
+ return "arm"
+ default:
+ return "unsupported"
+ }
+}
+
+func osString(os build.OperatingSystem) string {
+ switch os {
+ case build.Darwin:
+ return "darwin"
+ case build.Linux:
+ return "linux"
+ case build.Windows:
+ return "windows"
+ default:
+ return "unsupported"
+ }
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index 565d6d8..ed450c6 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -41,8 +41,7 @@
"time"
"veyron/lib/config"
- "veyron/services/mgmt/build"
- cbinary "veyron/services/mgmt/lib/binary"
+ blib "veyron/services/mgmt/lib/binary"
vexec "veyron/services/mgmt/lib/exec"
"veyron/services/mgmt/profile"
@@ -52,6 +51,7 @@
"veyron2/rt"
"veyron2/services/mgmt/application"
"veyron2/services/mgmt/binary"
+ "veyron2/services/mgmt/build"
"veyron2/services/mgmt/node"
"veyron2/services/mgmt/repository"
"veyron2/verror"
@@ -119,30 +119,30 @@
// TODO(jsimsa): Avoid computing the host node description from
// scratch if a recent cached copy exists.
func (i *invoker) computeNodeProfile() (*profile.Specification, error) {
- result := profile.Specification{Format: profile.Format{Attributes: make(map[string]string)}}
+ result := profile.Specification{}
// Find out what the supported file format, operating system, and
// architecture is.
switch runtime.GOOS {
- case "linux":
- result.Format.Name = build.ELF.String()
- result.Format.Attributes["os"] = build.LINUX.String()
case "darwin":
- result.Format.Name = build.MACH.String()
- result.Format.Attributes["os"] = build.DARWIN.String()
+ result.Format = build.MACH
+ result.OS = build.Darwin
+ case "linux":
+ result.Format = build.ELF
+ result.OS = build.Linux
case "windows":
- result.Format.Name = build.PE.String()
- result.Format.Attributes["os"] = build.WINDOWS.String()
+ result.Format = build.PE
+ result.OS = build.Windows
default:
return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
}
switch runtime.GOARCH {
case "amd64":
- result.Format.Attributes["arch"] = build.AMD64.String()
+ result.Arch = build.AMD64
case "arm":
- result.Format.Attributes["arch"] = build.AMD64.String()
+ result.Arch = build.ARM
case "x86":
- result.Format.Attributes["arch"] = build.AMD64.String()
+ result.Arch = build.X86
default:
return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
}
@@ -269,13 +269,13 @@
result := node.Description{Profiles: make(map[string]struct{})}
loop:
for _, profile := range known {
- if profile.Format.Name != p.Format.Name {
+ if profile.Format != p.Format {
continue
}
- if profile.Format.Attributes["os"] != p.Format.Attributes["os"] {
+ if profile.OS != p.OS {
continue
}
- if profile.Format.Attributes["arch"] != p.Format.Attributes["arch"] {
+ if profile.Arch != p.Arch {
continue
}
for library := range profile.Libraries {
@@ -331,7 +331,7 @@
// APPLICATION INTERFACE IMPLEMENTATION
func downloadBinary(workspace, name string) error {
- data, err := cbinary.Download(name)
+ data, err := blib.Download(name)
if err != nil {
vlog.Errorf("Download(%v) failed: %v", name, err)
return errOperationFailed
diff --git a/services/mgmt/profile/impl/impl_test.go b/services/mgmt/profile/impl/impl_test.go
index bb52ef9..ca9ff10 100644
--- a/services/mgmt/profile/impl/impl_test.go
+++ b/services/mgmt/profile/impl/impl_test.go
@@ -10,15 +10,18 @@
"veyron2/naming"
"veyron2/rt"
+ "veyron2/services/mgmt/build"
)
var (
// spec is an example profile specification used throughout the test.
spec = profile.Specification{
- Format: profile.Format{Name: "elf", Attributes: map[string]string{"os": "linux", "arch": "amd64"}},
+ Arch: build.AMD64,
+ Description: "Example profile to test the profile repository implementation.",
+ Format: build.ELF,
Libraries: map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
Label: "example",
- Description: "Example profile to test the profile repository implementation.",
+ OS: build.Linux,
}
)
diff --git a/services/mgmt/profile/profile.vdl b/services/mgmt/profile/profile.vdl
index d37c59b..0f6338b 100644
--- a/services/mgmt/profile/profile.vdl
+++ b/services/mgmt/profile/profile.vdl
@@ -2,16 +2,7 @@
// types used by the implementation of Veyron profiles.
package profile
-// Format includes a type (e.g. ELF) and each instance of the format
-// has some specific attributes. The key attributes are the target
-// operating system (e.g. for ELF this could be one of System V,
-// HP-UX, NetBSD, Linux, Solaris, AIX, IRIX, FreeBSD, and OpenBSD) and
-// the target instruction set architecture (e.g. for ELF this could be
-// one of SPARC, x86, PowerPC, ARM, IA-64, x86-64, and AArch64).
-type Format struct {
- Name string
- Attributes map[string]string
-}
+import "veyron2/services/mgmt/build"
// Library describes a shared library that applications may use.
type Library struct {
@@ -26,12 +17,17 @@
// Specification is how we represent a profile internally. It should
// provide enough information to allow matching of binaries to nodes.
type Specification struct {
- // Format is the file format of the application binary.
- Format Format
- // Libraries is a set of libraries the application binary depends on.
- Libraries set[Library]
- // A human-friendly concise label for the profile, e.g. "linux-media"
- Label string
- // A human-friendly description of the profile.
+ // Arch is the target hardware architecture of the profile.
+ Arch build.Architecture
+ // Description is a human-friendly description of the profile.
Description string
+ // Format is the file format supported by the profile.
+ Format build.Format
+ // Libraries is a set of libraries the profile requires.
+ Libraries set[Library]
+ // Label is a human-friendly concise label for the profile,
+ // e.g. "linux-media".
+ Label string
+ // OS is the target operating system of the profile.
+ OS build.OperatingSystem
}
diff --git a/services/mgmt/profile/profile.vdl.go b/services/mgmt/profile/profile.vdl.go
index c219f26..8ece520 100644
--- a/services/mgmt/profile/profile.vdl.go
+++ b/services/mgmt/profile/profile.vdl.go
@@ -5,16 +5,9 @@
// types used by the implementation of Veyron profiles.
package profile
-// Format includes a type (e.g. ELF) and each instance of the format
-// has some specific attributes. The key attributes are the target
-// operating system (e.g. for ELF this could be one of System V,
-// HP-UX, NetBSD, Linux, Solaris, AIX, IRIX, FreeBSD, and OpenBSD) and
-// the target instruction set architecture (e.g. for ELF this could be
-// one of SPARC, x86, PowerPC, ARM, IA-64, x86-64, and AArch64).
-type Format struct {
- Name string
- Attributes map[string]string
-}
+import (
+ "veyron2/services/mgmt/build"
+)
// Library describes a shared library that applications may use.
type Library struct {
@@ -29,12 +22,17 @@
// Specification is how we represent a profile internally. It should
// provide enough information to allow matching of binaries to nodes.
type Specification struct {
- // Format is the file format of the application binary.
- Format Format
- // Libraries is a set of libraries the application binary depends on.
- Libraries map[Library]struct{}
- // A human-friendly concise label for the profile, e.g. "linux-media"
- Label string
- // A human-friendly description of the profile.
+ // Arch is the target hardware architecture of the profile.
+ Arch build.Architecture
+ // Description is a human-friendly description of the profile.
Description string
+ // Format is the file format supported by the profile.
+ Format build.Format
+ // Libraries is a set of libraries the profile requires.
+ Libraries map[Library]struct{}
+ // Label is a human-friendly concise label for the profile,
+ // e.g. "linux-media".
+ Label string
+ // OS is the target operating system of the profile.
+ OS build.OperatingSystem
}
diff --git a/services/mgmt/repository/repository.vdl.go b/services/mgmt/repository/repository.vdl.go
index f6bcc37..83bc841 100644
--- a/services/mgmt/repository/repository.vdl.go
+++ b/services/mgmt/repository/repository.vdl.go
@@ -523,46 +523,42 @@
result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
result.Methods["Put"] = _gen_ipc.MethodSignature{
InArgs: []_gen_ipc.MethodArgument{
- {Name: "Specification", Type: 69},
+ {Name: "Specification", Type: 70},
},
OutArgs: []_gen_ipc.MethodArgument{
- {Name: "", Type: 70},
+ {Name: "", Type: 71},
},
}
result.Methods["Remove"] = _gen_ipc.MethodSignature{
InArgs: []_gen_ipc.MethodArgument{},
OutArgs: []_gen_ipc.MethodArgument{
- {Name: "", Type: 70},
+ {Name: "", Type: 71},
},
}
result.Methods["Specification"] = _gen_ipc.MethodSignature{
InArgs: []_gen_ipc.MethodArgument{},
OutArgs: []_gen_ipc.MethodArgument{
- {Name: "", Type: 69},
{Name: "", Type: 70},
+ {Name: "", Type: 71},
},
}
result.TypeDefs = []_gen_vdlutil.Any{
- _gen_wiretype.MapType{Key: 0x3, Elem: 0x3, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
- []_gen_wiretype.FieldType{
- _gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
- _gen_wiretype.FieldType{Type: 0x41, Name: "Attributes"},
- },
- "veyron/services/mgmt/profile.Format", []string(nil)},
- _gen_wiretype.StructType{
+ _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/services/mgmt/build.Architecture", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/services/mgmt/build.Format", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
_gen_wiretype.FieldType{Type: 0x3, Name: "Name"},
_gen_wiretype.FieldType{Type: 0x3, Name: "MajorVersion"},
_gen_wiretype.FieldType{Type: 0x3, Name: "MinorVersion"},
},
"veyron/services/mgmt/profile.Library", []string(nil)},
- _gen_wiretype.MapType{Key: 0x43, Elem: 0x2, Name: "", Tags: []string(nil)}, _gen_wiretype.StructType{
+ _gen_wiretype.MapType{Key: 0x43, Elem: 0x2, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "veyron2/services/mgmt/build.OperatingSystem", Tags: []string(nil)}, _gen_wiretype.StructType{
[]_gen_wiretype.FieldType{
+ _gen_wiretype.FieldType{Type: 0x41, Name: "Arch"},
+ _gen_wiretype.FieldType{Type: 0x3, Name: "Description"},
_gen_wiretype.FieldType{Type: 0x42, Name: "Format"},
_gen_wiretype.FieldType{Type: 0x44, Name: "Libraries"},
_gen_wiretype.FieldType{Type: 0x3, Name: "Label"},
- _gen_wiretype.FieldType{Type: 0x3, Name: "Description"},
+ _gen_wiretype.FieldType{Type: 0x45, Name: "OS"},
},
"veyron/services/mgmt/profile.Specification", []string(nil)},
_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
diff --git a/services/mounttable/lib/mounttable_test.go b/services/mounttable/lib/mounttable_test.go
index e961bc5..cfea7f8 100644
--- a/services/mounttable/lib/mounttable_test.go
+++ b/services/mounttable/lib/mounttable_test.go
@@ -29,9 +29,9 @@
}
var (
- rootID = veyron2.LocalID(security.FakePrivateID("root"))
- bobID = veyron2.LocalID(security.FakePrivateID("bob"))
- aliceID = veyron2.LocalID(security.FakePrivateID("alice"))
+ rootID = veyron2.LocalID(security.FakePublicID("root"))
+ bobID = veyron2.LocalID(security.FakePublicID("bob"))
+ aliceID = veyron2.LocalID(security.FakePublicID("alice"))
)
const ttlSecs = 60 * 60
@@ -169,7 +169,12 @@
}
func newMT(t *testing.T, acl string) (ipc.Server, string) {
- r := rt.Init()
+ // It is necessary for the private key of runtime's identity and
+ // the public key of the LocalIDOpts passed to clients to correspond.
+ // Since the LocalIDOpts are FakePublicIDs, we initialize the runtime
+ // below with a FakePrivateID. (Note all FakePublicIDs and FakePrivateIDs
+ // always have corresponding public and private keys respectively.)
+ r := rt.Init(veyron2.RuntimeID(security.FakePrivateID("irrelevant")))
server, err := r.NewServer(veyron2.ServesMountTableOpt(true))
if err != nil {
boom(t, "r.NewServer: %s", err)
diff --git a/services/mounttable/lib/neighborhood_test.go b/services/mounttable/lib/neighborhood_test.go
index 471edba..4c5bbb3 100644
--- a/services/mounttable/lib/neighborhood_test.go
+++ b/services/mounttable/lib/neighborhood_test.go
@@ -25,7 +25,7 @@
func TestNeighborhood(t *testing.T) {
r := rt.Init()
- id := veyron2.LocalID(rt.R().Identity())
+ id := veyron2.LocalID(rt.R().Identity().PublicID())
vlog.Infof("TestNeighborhood")
server, err := r.NewServer()
if err != nil {
diff --git a/services/security/discharger/discharger.go b/services/security/discharger/discharger.go
new file mode 100644
index 0000000..9bcb9ae
--- /dev/null
+++ b/services/security/discharger/discharger.go
@@ -0,0 +1,31 @@
+package discharger
+
+import (
+ "fmt"
+ "time"
+ ssecurity "veyron/services/security"
+ "veyron2/ipc"
+ "veyron2/security"
+ "veyron2/vdl/vdlutil"
+)
+
+// dischargerd issues discharges for all caveats present in the current
+// namespace with no additional caveats iff the caveat is valid.
+type dischargerd struct {
+ id security.PrivateID
+}
+
+// TODO(andreser,ataly): make it easier for third party public key caveats to specify the caveats on their discharges
+
+func (d dischargerd) Discharge(ctx ipc.ServerContext, caveatAny vdlutil.Any) (vdlutil.Any, error) {
+ caveat, ok := caveatAny.(security.ThirdPartyCaveat)
+ if !ok {
+ return nil, fmt.Errorf("type %T does not implement security.ThirdPartyCaveat", caveatAny)
+ }
+ return d.id.MintDischarge(caveat, ctx, time.Minute, nil)
+}
+
+// New returns a Discharger server that can be passed to a dispatcher
+func New(id security.PrivateID) interface{} {
+ return ssecurity.NewServerDischarger(&dischargerd{id})
+}
diff --git a/services/security/discharger/revoker.go b/services/security/discharger/revoker.go
new file mode 100644
index 0000000..4f32574
--- /dev/null
+++ b/services/security/discharger/revoker.go
@@ -0,0 +1,123 @@
+package discharger
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "path/filepath"
+ "strings"
+ "sync"
+ "veyron/security/caveat"
+ ssecurity "veyron/services/security"
+ "veyron2/ipc"
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/security"
+ "veyron2/storage"
+ "veyron2/storage/vstore"
+ "veyron2/storage/vstore/primitives"
+ "veyron2/vlog"
+ "veyron2/vom"
+)
+
+// TODO(ataly, andreser) This package uses a global variable to store the
+// revoker state to make it accessible to caveat.Validate. Ideally, we would
+// pass this in through the context (or something equivalent).
+
+type revocationServiceT struct {
+ store storage.Store
+ pathInStore string
+}
+
+var revocationService struct {
+ *revocationServiceT
+ sync.Mutex
+}
+
+type revocationCaveat [32]byte
+
+func (cav revocationCaveat) Validate(security.Context) error {
+ // TODO(ashankar,mattr): Figure out how to get the context of an existing RPC here
+ rctx := rt.R().NewContext()
+ revocation := revocationService.store.Bind(naming.Join(revocationService.pathInStore,
+ hex.EncodeToString(cav[:])))
+ tx := primitives.NewTransaction(rctx)
+ defer tx.Abort(rctx)
+ exists, err := revocation.Exists(rctx, tx)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return fmt.Errorf("revoked")
+ }
+ return nil
+}
+
+// NewRevocationCaveat returns a security.ThirdPartyCaveat that discharger will
+// mint discharges until explicitly told not to by calling Revoke on it
+// (using the returned revocation token)
+func NewRevocationCaveat(dischargerID security.PublicID, dischargerLocation string) (ssecurity.RevocationToken, security.ThirdPartyCaveat, error) {
+ var revocation ssecurity.RevocationToken
+ if _, err := rand.Read(revocation[:]); err != nil {
+ return revocation, nil, err
+ }
+ restriction := revocationCaveat(sha256.Sum256(revocation[:]))
+ cav, err := caveat.NewPublicKeyCaveat(restriction, dischargerID, dischargerLocation)
+ return revocation, cav, err
+}
+
+func (revoceationService *revocationServiceT) Revoke(ctx ipc.ServerContext, caveatPreimage ssecurity.RevocationToken) error {
+ caveatNonce := sha256.Sum256(caveatPreimage[:])
+ tx := primitives.NewTransaction(ctx)
+ revocation := revocationService.store.Bind(naming.Join(revocationService.pathInStore, hex.EncodeToString(caveatNonce[:])))
+ if _, err := revocation.Put(ctx, tx, caveatPreimage[:]); err != nil {
+ tx.Abort(ctx)
+ return err
+ }
+ return tx.Commit(ctx)
+}
+
+// NewRevoker returns a new revoker service that can be passed to a dispatcher.
+// Currently, due to the use of global variables, this function can be called only once.
+func NewRevoker(storeName, pathInStore string) (interface{}, error) {
+ revocationService.Lock()
+ defer revocationService.Unlock()
+ if revocationService.revocationServiceT != nil {
+ return nil, fmt.Errorf("revoker.Revoker called more than once")
+ }
+ var err error
+ revocationService.revocationServiceT = new(revocationServiceT)
+ revocationService.store, err = vstore.New(storeName)
+ if err != nil {
+ return nil, err
+ }
+
+ rctx := rt.R().NewContext()
+ tx := primitives.NewTransaction(rctx)
+
+ // Create parent directories for the revoker root, if necessary
+ // TODO(tilaks,andreser): provide a `mkdir -p` equivalent in store
+ l := strings.Split(pathInStore, "/")
+ fmt.Println(l)
+ for i := 0; i <= len(l); i++ {
+ fmt.Println(i, filepath.Join(l[:i]...))
+ prefix := filepath.Join(l[:i]...)
+ o := revocationService.store.Bind(prefix)
+ if exist, err := o.Exists(rctx, tx); err != nil {
+ vlog.Infof("Error checking existence at %q: %s", prefix, err)
+ } else if !exist {
+ if _, err := o.Put(rctx, tx, &Dir{}); err != nil {
+ vlog.Infof("Error creating directory %q: %s", prefix, err)
+ }
+ }
+ }
+ if err := tx.Commit(rctx); err != nil {
+ vlog.Fatalf("Commit creation of revocer root et %s: %s", pathInStore, err)
+ }
+ return ssecurity.NewServerRevoker(revocationService.revocationServiceT), nil
+}
+
+func init() {
+ vom.Register(revocationCaveat{})
+}
diff --git a/services/security/discharger/revoker_test.go b/services/security/discharger/revoker_test.go
new file mode 100644
index 0000000..acb89e9
--- /dev/null
+++ b/services/security/discharger/revoker_test.go
@@ -0,0 +1,97 @@
+package discharger
+
+import (
+ "testing"
+ ssecurity "veyron/services/security"
+ teststore "veyron/services/store/testutil"
+ "veyron2/ipc"
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/security"
+)
+
+func init() {
+ rt.Init()
+}
+
+func setup(t *testing.T) (dischargerID security.PublicID, dischargerEndpoint, revokerEndpoint string, closeFunc func()) {
+ // Create and start the store instance that the revoker will use
+ storeServer, err := rt.R().NewServer()
+ if err != nil {
+ t.Fatalf("rt.R().NewServer: %s", err)
+ }
+ storeVeyronName, closeStore := teststore.NewStore(t, storeServer, rt.R().Identity().PublicID())
+
+ // Create and start revoker and revocation discharge service
+ revokerServer, err := rt.R().NewServer()
+ if err != nil {
+ t.Fatalf("rt.R().NewServer: %s", err)
+ }
+ revokerEP, err := revokerServer.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("revokerServer.Listen failed: %v", err)
+ }
+ revokerService, err := NewRevoker(storeVeyronName, "/testrevoker")
+ if err != nil {
+ t.Fatalf("setup revoker service: %s", err)
+ }
+ err = revokerServer.Serve("", ipc.SoloDispatcher(revokerService, nil))
+ if err != nil {
+ t.Fatalf("revokerServer.Serve discharger: %s", err)
+ }
+
+ dischargerServer, err := rt.R().NewServer()
+ if err != nil {
+ t.Fatalf("rt.R().NewServer: %s", err)
+ }
+ dischargerEP, err := dischargerServer.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("revokerServer.Listen failed: %v", err)
+ }
+ err = dischargerServer.Serve("", ipc.SoloDispatcher(New(rt.R().Identity()), nil))
+ if err != nil {
+ t.Fatalf("revokerServer.Serve revoker: %s", err)
+ }
+ return rt.R().Identity().PublicID(),
+ naming.JoinAddressName(dischargerEP.String(), ""),
+ naming.JoinAddressName(revokerEP.String(), ""),
+ func() {
+ revokerServer.Stop()
+ dischargerServer.Stop()
+ closeStore()
+ }
+}
+
+func TestDischargeRevokeDischargeRevokeDischarge(t *testing.T) {
+ dcID, dc, rv, closeFunc := setup(t)
+ defer closeFunc()
+ revoker, err := ssecurity.BindRevoker(rv)
+ if err != nil {
+ t.Fatalf("error binding to server: ", err)
+ }
+ discharger, err := ssecurity.BindDischarger(dc)
+ if err != nil {
+ t.Fatalf("error binding to server: ", err)
+ }
+
+ preimage, cav, err := NewRevocationCaveat(dcID, dc)
+ if err != nil {
+ t.Fatalf("failed to create public key caveat: %s", err)
+ }
+
+ if _, err = discharger.Discharge(rt.R().NewContext(), cav); err != nil {
+ t.Fatalf("failed to get discharge: %s", err)
+ }
+ if err = revoker.Revoke(rt.R().NewContext(), preimage); err != nil {
+ t.Fatalf("failed to revoke: %s", err)
+ }
+ if discharge, err := discharger.Discharge(rt.R().NewContext(), cav); err == nil || discharge != nil {
+ t.Fatalf("got a discharge for a revoked caveat: %s", err)
+ }
+ if err = revoker.Revoke(rt.R().NewContext(), preimage); err != nil {
+ t.Fatalf("failed to revoke again: %s", err)
+ }
+ if discharge, err := discharger.Discharge(rt.R().NewContext(), cav); err == nil || discharge != nil {
+ t.Fatalf("got a discharge for a doubly revoked caveat: %s", err)
+ }
+}
diff --git a/services/security/discharger/schema.vdl b/services/security/discharger/schema.vdl
new file mode 100644
index 0000000..d92d035
--- /dev/null
+++ b/services/security/discharger/schema.vdl
@@ -0,0 +1,3 @@
+package discharger
+
+type Dir struct {} // TODO(tilaks, andreser): move this to store?
diff --git a/services/security/discharger/schema.vdl.go b/services/security/discharger/schema.vdl.go
new file mode 100644
index 0000000..462d255
--- /dev/null
+++ b/services/security/discharger/schema.vdl.go
@@ -0,0 +1,7 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: schema.vdl
+
+package discharger
+
+type Dir struct {
+} // TODO(tilaks, andreser): move this to store?
diff --git a/services/security/dischargerd/main.go b/services/security/dischargerd/main.go
new file mode 100644
index 0000000..cde9103
--- /dev/null
+++ b/services/security/dischargerd/main.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "flag"
+
+ "veyron/lib/signals"
+ "veyron/services/security/discharger"
+ "veyron2/ipc"
+ "veyron2/rt"
+ "veyron2/security"
+ "veyron2/vlog"
+)
+
+var (
+ protocol = flag.String("protocol", "tcp", "protocol to listen on")
+ address = flag.String("address", ":0", "address to listen on")
+ aclFile = flag.String("discharger-acl", "", "ACL to use for the discharge service")
+ publish = flag.String("publish", "discharger", "the Object Name under which to publish this service")
+
+ storeName = flag.String("revocation-store", "", "Object Name of the Veyron store to be used for revocation. Omit to disable revocation functionality.")
+ publishRevoker = flag.String("publish-revoker", "revoker", "the Object Name under which to publish this service")
+ pathInStore = flag.String("path-in-store", "/revoker", "the location in store where the revoker keeps its state")
+ revokerAclFile = flag.String("revoker-acl", "", "ACL to use for the revocation service")
+)
+
+func authorizer(file string) security.Authorizer {
+ if file == "" {
+ return security.NewACLAuthorizer(security.ACL{security.AllPrincipals: security.AllLabels})
+ }
+ return security.NewFileACLAuthorizer(file)
+}
+
+func main() {
+ r := rt.Init()
+ defer r.Cleanup()
+
+ dischargerServer, err := r.NewServer()
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ defer dischargerServer.Stop()
+ dischargerEndpoint, err := dischargerServer.Listen(*protocol, *address)
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ if err = dischargerServer.Serve(*publish, ipc.SoloDispatcher(discharger.New(r.Identity()), authorizer(*aclFile))); err != nil {
+ vlog.Fatal(err)
+ }
+ vlog.Infof("discharger: %s", dischargerEndpoint.String())
+
+ if *storeName != "" {
+ revokerServer, err := r.NewServer()
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ defer revokerServer.Stop()
+ revokerEndpoint, err := revokerServer.Listen(*protocol, *address)
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ revokerService, err := discharger.NewRevoker(*storeName, *pathInStore)
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ err = revokerServer.Serve(*publish, ipc.SoloDispatcher(revokerService, authorizer(*revokerAclFile)))
+ if err != nil {
+ vlog.Fatal(err)
+ }
+ vlog.Infof("revoker: %s", revokerEndpoint.String())
+ }
+
+ <-signals.ShutdownOnSignals()
+}
diff --git a/services/security/revoker.vdl b/services/security/revoker.vdl
new file mode 100644
index 0000000..da13597
--- /dev/null
+++ b/services/security/revoker.vdl
@@ -0,0 +1,27 @@
+package security
+
+import "veyron2/security"
+
+// RevocationToken can be presented to a revocation service to revoke a caveat
+type RevocationToken [16]byte
+
+// Revoker is the interface for preventing discharges from being issued. The
+// dicharger ensures that no discharges will be issued for caveats that
+// have been explicitly revoked using this interface. To prevent discharge
+// stealing caveats just have to be unique; the exact structure is not relevant
+// to the client or the verifier. To make Revoker's job easy, each caveat
+// contains a SHA256 hash of its revocation token. To revoke a caveat C and
+// have it added to the discharger's blacklist, one simply needs to call
+// Revoke(x) with an x s.t. SHA256(x) = C. All caveats for which this has not
+// been revoked will get discharges, irrespective of who created them. This
+// means that the existence of a valid discharge does not imply that a
+// corresponding caveat exists, and even if it does, it may not be meant for
+// use with this revocation service. Just looking at discharges is meaningless,
+// a valid (Caveat, Discharge) pair is what can be relied on for
+// authentication. Not keeping track of non-revoked caveats enables
+// performance improvements on the Discharger side.
+type Revoker interface {
+ // Revoke ensures that iff a nil is returned, all discharge requests to the
+ // caveat with nonce sha256(caveatPreimage) are going to be denied.
+ Revoke(caveatPreimage RevocationToken) error {security.WriteLabel}
+}
diff --git a/services/security/revoker.vdl.go b/services/security/revoker.vdl.go
new file mode 100644
index 0000000..4e0bdaa
--- /dev/null
+++ b/services/security/revoker.vdl.go
@@ -0,0 +1,202 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: revoker.vdl
+
+package security
+
+import (
+ "veyron2/security"
+
+ // 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_vdlutil "veyron2/vdl/vdlutil"
+ _gen_wiretype "veyron2/wiretype"
+)
+
+// RevocationToken can be presented to a revocation service to revoke a caveat
+type RevocationToken [16]byte
+
+// Revoker is the interface for preventing discharges from being issued. The
+// dicharger ensures that no discharges will be issued for caveats that
+// have been explicitly revoked using this interface. To prevent discharge
+// stealing caveats just have to be unique; the exact structure is not relevant
+// to the client or the verifier. To make Revoker's job easy, each caveat
+// contains a SHA256 hash of its revocation token. To revoke a caveat C and
+// have it added to the discharger's blacklist, one simply needs to call
+// Revoke(x) with an x s.t. SHA256(x) = C. All caveats for which this has not
+// been revoked will get discharges, irrespective of who created them. This
+// means that the existence of a valid discharge does not imply that a
+// corresponding caveat exists, and even if it does, it may not be meant for
+// use with this revocation service. Just looking at discharges is meaningless,
+// a valid (Caveat, Discharge) pair is what can be relied on for
+// authentication. Not keeping track of non-revoked caveats enables
+// performance improvements on the Discharger side.
+// Revoker is the interface the client binds and uses.
+// Revoker_ExcludingUniversal is the interface without internal framework-added methods
+// to enable embedding without method collisions. Not to be used directly by clients.
+type Revoker_ExcludingUniversal interface {
+ // Revoke ensures that iff a nil is returned, all discharge requests to the
+ // caveat with nonce sha256(caveatPreimage) are going to be denied.
+ Revoke(ctx _gen_context.T, caveatPreimage RevocationToken, opts ..._gen_ipc.CallOpt) (err error)
+}
+type Revoker interface {
+ _gen_ipc.UniversalServiceMethods
+ Revoker_ExcludingUniversal
+}
+
+// RevokerService is the interface the server implements.
+type RevokerService interface {
+
+ // Revoke ensures that iff a nil is returned, all discharge requests to the
+ // caveat with nonce sha256(caveatPreimage) are going to be denied.
+ Revoke(context _gen_ipc.ServerContext, caveatPreimage RevocationToken) (err error)
+}
+
+// BindRevoker returns the client stub implementing the Revoker
+// interface.
+//
+// If no _gen_ipc.Client is specified, the default _gen_ipc.Client in the
+// global Runtime is used.
+func BindRevoker(name string, opts ..._gen_ipc.BindOpt) (Revoker, 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_vdlutil.ErrUnrecognizedOption
+ }
+ default:
+ return nil, _gen_vdlutil.ErrTooManyOptionsToBind
+ }
+ stub := &clientStubRevoker{client: client, name: name}
+
+ return stub, nil
+}
+
+// NewServerRevoker creates a new server stub.
+//
+// It takes a regular server implementing the RevokerService
+// interface, and returns a new server stub.
+func NewServerRevoker(server RevokerService) interface{} {
+ return &ServerStubRevoker{
+ service: server,
+ }
+}
+
+// clientStubRevoker implements Revoker.
+type clientStubRevoker struct {
+ client _gen_ipc.Client
+ name string
+}
+
+func (__gen_c *clientStubRevoker) Revoke(ctx _gen_context.T, caveatPreimage RevocationToken, opts ..._gen_ipc.CallOpt) (err error) {
+ var call _gen_ipc.Call
+ if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Revoke", []interface{}{caveatPreimage}, opts...); err != nil {
+ return
+ }
+ if ierr := call.Finish(&err); ierr != nil {
+ err = ierr
+ }
+ return
+}
+
+func (__gen_c *clientStubRevoker) 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 *clientStubRevoker) 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 *clientStubRevoker) 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
+}
+
+// ServerStubRevoker wraps a server that implements
+// RevokerService and provides an object that satisfies
+// the requirements of veyron2/ipc.ReflectInvoker.
+type ServerStubRevoker struct {
+ service RevokerService
+}
+
+func (__gen_s *ServerStubRevoker) 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 "Revoke":
+ return []interface{}{security.Label(2)}, nil
+ default:
+ return nil, nil
+ }
+}
+
+func (__gen_s *ServerStubRevoker) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
+ result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
+ result.Methods["Revoke"] = _gen_ipc.MethodSignature{
+ InArgs: []_gen_ipc.MethodArgument{
+ {Name: "caveatPreimage", Type: 66},
+ },
+ OutArgs: []_gen_ipc.MethodArgument{
+ {Name: "", Type: 67},
+ },
+ }
+
+ result.TypeDefs = []_gen_vdlutil.Any{
+ _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.ArrayType{Elem: 0x41, Len: 0x10, Name: "veyron/services/security.RevocationToken", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
+
+ return result, nil
+}
+
+func (__gen_s *ServerStubRevoker) 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 *ServerStubRevoker) Revoke(call _gen_ipc.ServerCall, caveatPreimage RevocationToken) (err error) {
+ err = __gen_s.service.Revoke(call, caveatPreimage)
+ return
+}
diff --git a/services/security/simpledischarged/main.go b/services/security/simpledischarged/main.go
deleted file mode 100644
index 427942d..0000000
--- a/services/security/simpledischarged/main.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "log"
- "time"
-
- "veyron/lib/signals"
- isecurity "veyron/services/security"
- "veyron2/ipc"
- "veyron2/rt"
- "veyron2/security"
- "veyron2/vdl/vdlutil"
-)
-
-// TODO(ataly, andreser): ideally, the expiration time (and other caveats) of
-// the discharge would be determined by a function much like ThirdPartyCaveat.Validate
-var expiration = flag.String("expiration", "10s", "time interval after which a discharge will expire")
-var protocol = flag.String("protocol", "bluetooth", "protocol to listen on")
-var address = flag.String("address", "", "address to listen on")
-var port = flag.Int("port", 0, "port to listen on")
-var publish = flag.String("publish", "", "the namespace where to publish this service")
-
-type dischargeAuthorizer struct{}
-
-func (dischargeAuthorizer) Authorize(c security.Context) error {
- if c.Method() == "Discharge" {
- return nil
- }
- return fmt.Errorf("Only authorized for method \"Discharge\"")
-}
-
-// discharged implements ipc.Dispatcher. It issues discharges for all caveats
-// present in the current namespace with no additional caveats iff the caveat
-// is valid.
-type discharged struct {
- id security.PrivateID
- expiration time.Duration
-}
-
-func (d *discharged) Discharge(ctx ipc.ServerContext, Caveat vdlutil.Any) (
- Discharge vdlutil.Any, err error) {
- caveat, ok := Caveat.(security.ThirdPartyCaveat)
- if !ok {
- err = errors.New("unknown caveat")
- return
- }
- return d.id.MintDischarge(caveat, ctx, d.expiration, nil)
-}
-
-func main() {
- flag.Parse()
- expiration, err := time.ParseDuration(*expiration)
- if err != nil {
- log.Fatalf("--expiration: ", err)
- }
-
- r := rt.Init()
- server, err := r.NewServer()
- if err != nil {
- log.Fatal(err)
- }
-
- discharger := isecurity.NewServerDischarger(&discharged{
- id: r.Identity(), expiration: expiration})
- dispatcher := ipc.SoloDispatcher(discharger, dischargeAuthorizer{})
- endpoint, err := server.Listen(*protocol, *address+":"+fmt.Sprint(*port))
- if err != nil {
- log.Fatal(err)
- }
- if err := server.Serve(*publish, dispatcher); err != nil {
- log.Fatal(err)
- }
- fmt.Println(endpoint)
- <-signals.ShutdownOnSignals()
-}
diff --git a/services/store/memstore/blackbox/team_player_test.go b/services/store/memstore/blackbox/team_player_test.go
index fc9c3d0..61a1255 100644
--- a/services/store/memstore/blackbox/team_player_test.go
+++ b/services/store/memstore/blackbox/team_player_test.go
@@ -122,7 +122,9 @@
// Iterate over the rockets.
players := make(map[storage.ID]*Player)
name := storage.ParsePath("/teamsapp/players")
- for it := st.Snapshot().NewIterator(rootPublicID, name, nil); it.IsValid(); it.Next() {
+ for it := st.Snapshot().NewIterator(rootPublicID, name,
+ state.ListPaths, nil); it.IsValid(); it.Next() {
+
e := it.Get()
if p, ok := e.Value.(*Player); ok {
if _, ok := players[e.Stat.ID]; ok {
@@ -148,7 +150,9 @@
// Iterate over all teams, nonrecursively.
teams := make(map[storage.ID]*Team)
name = storage.ParsePath("/teamsapp/teams")
- for it := st.Snapshot().NewIterator(rootPublicID, name, state.ImmediateFilter); it.IsValid(); it.Next() {
+ for it := st.Snapshot().NewIterator(rootPublicID, name,
+ state.ListPaths, state.ImmediateFilter); it.IsValid(); it.Next() {
+
e := it.Get()
v := e.Value
if _, ok := v.(*Player); ok {
@@ -173,17 +177,19 @@
}
// Iterate over all teams, recursively.
- playerCount := 0
+ contractCount := 0
teamCount := 0
players = make(map[storage.ID]*Player)
teams = make(map[storage.ID]*Team)
name = storage.ParsePath("/teamsapp/teams")
- for it := st.Snapshot().NewIterator(rootPublicID, name, nil); it.IsValid(); it.Next() {
+ for it := st.Snapshot().NewIterator(rootPublicID, name,
+ state.ListPaths, nil); it.IsValid(); it.Next() {
+
e := it.Get()
v := e.Value
if p, ok := v.(*Player); ok {
players[e.Stat.ID] = p
- playerCount++
+ contractCount++
}
if team, ok := v.(*Team); ok {
teams[e.Stat.ID] = team
@@ -202,8 +208,8 @@
if team, ok := teams[hornetsID]; !ok || team.FullName != "Hornets" {
t.Errorf("Should have Hornets, have %v", team)
}
- if playerCount != 3 {
- t.Errorf("Should have 3 players: have %d", playerCount)
+ if contractCount != 4 {
+ t.Errorf("Should have 4 contracts: have %d", contractCount)
}
if len(players) != 3 {
t.Errorf("Should have 3 players: have %v", players)
diff --git a/services/store/memstore/query/eval.go b/services/store/memstore/query/eval.go
index cd89615..73fa455 100644
--- a/services/store/memstore/query/eval.go
+++ b/services/store/memstore/query/eval.go
@@ -63,6 +63,12 @@
results <-chan *store.QueryResult
}
+// hiddenResult wraps a value so evalIterator can elide it from
+// storage.QueryResult.Fields that are sent to the client.
+type hiddenResult struct {
+ value vdlutil.Any
+}
+
// Next implements the QueryStream method.
func (it *evalIterator) Next() bool {
it.mu.Lock()
@@ -102,17 +108,23 @@
// the result.Fields key to append them in reverse alphabetical order.
// We use reverse alphabetical order because it.results is a stack--
// we want to process them in alphabetical order.
+//
+// enqueueNestedChannels also removes any result.Fields that are of the
+// type hiddenResult.
func (it *evalIterator) enqueueNestedChannels(result *store.QueryResult) {
if result.Fields == nil {
return
}
var nestingKeys []string
for key, val := range result.Fields {
- // TODO(kash): If a stored value happens to be a store.QueryResult, we'll
- // do the wrong thing here. Once we store vom.Value instead of raw structs,
- // this should not be a problem.
- if _, ok := val.(chan *store.QueryResult); ok {
+ switch val.(type) {
+ case chan *store.QueryResult:
nestingKeys = append(nestingKeys, key)
+ case hiddenResult:
+ // If a field is "hidden", the value will be wrapped in the type
+ // hiddenResult to make it possible for evalIterator to elide it
+ // from the results sent to the client.
+ delete(result.Fields, key)
}
}
// Figure out the store.NestedResult values based on alphabetical order of
@@ -357,7 +369,9 @@
path := storage.ParsePath(naming.Join(c.suffix, basepath))
vlog.VI(2).Infof("nameEvaluator suffix: %s, result.Name: %s, VName: %s",
c.suffix, result.Name, e.wildcardName.VName)
- for it := c.sn.NewIterator(c.clientID, path, state.ImmediateFilter); it.IsValid(); it.Next() {
+ for it := c.sn.NewIterator(c.clientID, path,
+ state.ListObjects, state.ImmediateFilter); it.IsValid(); it.Next() {
+
entry := it.Get()
result := &store.QueryResult{
Name: naming.Join(basepath, it.Name()),
@@ -502,7 +516,6 @@
alias string
// hidden is true if this field in the selection should not be included
// in the results sent to the client.
- // TODO(kash): hidden is currently ignored during evaluation.
hidden bool
}
@@ -577,6 +590,12 @@
}
if a.alias != "" {
+ if a.hidden {
+ // If a field is "hidden", the value will be wrapped in the type
+ // hiddenResult to make it possible for evalIterator to elide it
+ // from the results sent to the client.
+ value = hiddenResult{value}
+ }
sel.Fields[a.alias] = value
} else {
sel.Fields[a.evaluator.name()] = value
@@ -1019,6 +1038,12 @@
e.name, result.Name, mapKeys(result.Fields))
return nil
}
+ // If a field is "hidden", the value will be wrapped in the type
+ // hiddenResult to make it possible for evalIterator to elide it
+ // from the results sent to the client.
+ if v, ok := val.(hiddenResult); ok {
+ return v.value
+ }
return val
}
fullpath := naming.Join(result.Name, e.name)
diff --git a/services/store/memstore/query/eval_test.go b/services/store/memstore/query/eval_test.go
index 282eaa0..6367a9f 100644
--- a/services/store/memstore/query/eval_test.go
+++ b/services/store/memstore/query/eval_test.go
@@ -358,6 +358,19 @@
},
},
{
+ "", "'teams/cardinals' | {Name as myname hidden, Location as myloc} | ? myname == 'cardinals'",
+ []*store.QueryResult{
+ &store.QueryResult{
+ 0,
+ "teams/cardinals",
+ map[string]vdlutil.Any{
+ "myloc": "CA",
+ },
+ nil,
+ },
+ },
+ },
+ {
"",
"'teams/*' | type team | {" +
" Name as myname," +
@@ -434,7 +447,7 @@
break
}
if got, want := result, test.expectedResults[i]; !reflect.DeepEqual(got, want) {
- t.Errorf("query: %s;\nGOT %s\nWANT %s", test.query, got, want)
+ t.Errorf("query: %s;\nGOT %v\nWANT %v", test.query, got, want)
}
i++
}
@@ -492,7 +505,7 @@
it state.Iterator
}
-func (m *mockSnapshot) NewIterator(pid security.PublicID, path storage.PathName, filter state.IterFilter) state.Iterator {
+func (m *mockSnapshot) NewIterator(pid security.PublicID, path storage.PathName, pathFilter state.PathFilter, filter state.IterFilter) state.Iterator {
return m.it
}
diff --git a/services/store/memstore/query/glob.go b/services/store/memstore/query/glob.go
index 3614af3..8b1722e 100644
--- a/services/store/memstore/query/glob.go
+++ b/services/store/memstore/query/glob.go
@@ -32,7 +32,7 @@
pathLen: len(path),
glob: parsed,
}
- g.Iterator = sn.NewIterator(clientID, path, state.IterFilter(g.filter))
+ g.Iterator = sn.NewIterator(clientID, path, state.ListPaths, state.IterFilter(g.filter))
return g, nil
}
diff --git a/services/store/memstore/query/glob_test.go b/services/store/memstore/query/glob_test.go
index 70ff580..6690d98 100644
--- a/services/store/memstore/query/glob_test.go
+++ b/services/store/memstore/query/glob_test.go
@@ -9,51 +9,64 @@
"veyron2/storage"
)
-type nameOptions []string
-
type globTest struct {
path string
pattern string
- expected []nameOptions
+ expected []string
}
var globTests = []globTest{
- {"", "mvps/...", []nameOptions{
- {"mvps"},
- {"mvps/Links/0"},
- {"mvps/Links/1"},
+ {"", "...", []string{
+ "",
+ "mvps",
+ "mvps/Links/0",
+ "mvps/Links/1",
+ "players",
+ "players/alfred",
+ "players/alice",
+ "players/betty",
+ "players/bob",
+ "teams",
+ "teams/bears",
+ "teams/cardinals",
+ "teams/sharks",
}},
- {"", "players/...", []nameOptions{
- {"players"},
- {"players/alfred"},
- {"players/alice"},
- {"players/betty"},
- {"players/bob"},
+ {"", "mvps/...", []string{
+ "mvps",
+ "mvps/Links/0",
+ "mvps/Links/1",
+ }},
+ {"", "players/...", []string{
+ "players",
+ "players/alfred",
+ "players/alice",
+ "players/betty",
+ "players/bob",
}},
// Note(mattr): This test case shows that Glob does not return
// subfield nodes.
- {"", "mvps/*", []nameOptions{}},
- {"", "mvps/Links/*", []nameOptions{
- {"mvps/Links/0"},
- {"mvps/Links/1"},
+ {"", "mvps/*", []string{}},
+ {"", "mvps/Links/*", []string{
+ "mvps/Links/0",
+ "mvps/Links/1",
}},
- {"", "players/alfred", []nameOptions{
- {"players/alfred"},
+ {"", "players/alfred", []string{
+ "players/alfred",
}},
- {"", "mvps/Links/0", []nameOptions{
- {"mvps/Links/0"},
+ {"", "mvps/Links/0", []string{
+ "mvps/Links/0",
}},
// An empty pattern returns the element referred to by the path.
- {"/mvps/Links/0", "", []nameOptions{
- {""},
+ {"/mvps/Links/0", "", []string{
+ "",
}},
- {"mvps", "Links/*", []nameOptions{
- {"Links/0"},
- {"Links/1"},
+ {"mvps", "Links/*", []string{
+ "Links/0",
+ "Links/1",
}},
- {"mvps/Links", "*", []nameOptions{
- {"0"},
- {"1"},
+ {"mvps/Links", "*", []string{
+ "0",
+ "1",
}},
}
@@ -102,16 +115,9 @@
t.Errorf("Wrong number of names for %s. got %v, wanted %v",
gt.pattern, names, gt.expected)
}
- for _, options := range gt.expected {
- found := false
- for _, name := range options {
- if names[name] {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("Expected to find one of %v in %v", options, names)
+ for _, name := range gt.expected {
+ if !names[name] {
+ t.Errorf("Expected to find %v in %v", name, names)
}
}
}
diff --git a/services/store/memstore/state/iterator.go b/services/store/memstore/state/iterator.go
index 92614be..70eac5c 100644
--- a/services/store/memstore/state/iterator.go
+++ b/services/store/memstore/state/iterator.go
@@ -37,10 +37,13 @@
type iterator struct {
snapshot Snapshot
- // Set of IDs already visited.
+ // Set of IDs already visited on this path.
visited map[storage.ID]struct{}
- // Stack of IDs to visit next. Some of these may already have been visited.
+ // Stack of actions to consider next. Actions are one of:
+ // - visit a node accessible from the current path (the node may already
+ // have been visited on the current path).
+ // - unvisit a node (backtrack the current path).
next []next
// Depth of starting path.
@@ -50,6 +53,8 @@
entry *storage.Entry
path *refs.FullPath
+ pathFilter PathFilter
+
filter IterFilter
}
@@ -59,13 +64,32 @@
parent *refs.FullPath
path *refs.Path
id storage.ID
+ action action
}
+type action int
+
+const (
+ visit = action(iota)
+ unvisit
+)
+
var (
_ Iterator = (*iterator)(nil)
_ Iterator = (*errorIterator)(nil)
)
+// A PathFilter automatically limits the traversal of certain paths,
+type PathFilter int
+
+const (
+ // ListPaths permits any path that does not visit the same object twice.
+ ListPaths = PathFilter(iota)
+ // ListObjects permits any path that does not revisit any object on a
+ // previously traversed path 'Q', even if Q did not satisfy it.filter.
+ ListObjects
+)
+
// An IterFilter examines entries as they are considered by the
// iterator and allows it to give two boolean inputs to the process:
// ret: True if the iterator should return this value in its iteration.
@@ -84,11 +108,14 @@
return true, path == nil
}
-// NewIterator returns an Iterator that starts with the value at
-// <path>. If filter is given it is used to limit traversal beneath
-// certain paths. filter can be specified to limit the results of the iteration.
-// If filter is nil, all decendents of the specified path are returned.
-func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName, filter IterFilter) Iterator {
+// NewIterator returns an Iterator that starts with the value at <path>.
+// pathFilter is used to automatically limit traversal of certain paths.
+// If filter is given, it is used to limit traversal beneath certain paths and
+// limit the results of the iteration. If filter is nil, all decendents of the
+// specified path are returned.
+func (sn *snapshot) NewIterator(pid security.PublicID, path storage.PathName,
+ pathFilter PathFilter, filter IterFilter) Iterator {
+
checker := sn.newPermChecker(pid)
cell, suffix, v := sn.resolveCell(checker, path, nil)
if cell == nil {
@@ -104,6 +131,7 @@
visited: make(map[storage.ID]struct{}),
initialDepth: len(path),
path: refs.NewFullPathFromName(path),
+ pathFilter: pathFilter,
filter: filter,
}
@@ -120,11 +148,12 @@
} else {
it.entry = cell.GetEntry()
it.visited[cell.ID] = struct{}{}
+ it.pushUnvisit(nil, cell.ID)
set = cell.refs
}
if expand {
- it.pushAll(checker, it.path, set)
+ it.pushVisitAll(checker, it.path, set)
}
if !ret {
it.Next()
@@ -133,11 +162,25 @@
return it
}
-func (it *iterator) pushAll(checker *acl.Checker, parentPath *refs.FullPath, set refs.Set) {
+func (it *iterator) pushUnvisit(path *refs.Path, id storage.ID) {
+ switch it.pathFilter {
+ case ListPaths:
+ it.next = append(it.next, next{nil, nil, path, id, unvisit})
+ case ListObjects:
+ // Do not unvisit the object, as it is on a path already seen by
+ // it.filter.
+ default:
+ panic("unknown PathFilter")
+ }
+}
+
+func (it *iterator) pushVisitAll(checker *acl.Checker,
+ parentPath *refs.FullPath, set refs.Set) {
+
set.Iter(func(x interface{}) bool {
ref := x.(*refs.Ref)
if checker.IsAllowed(ref.Label) {
- it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID})
+ it.next = append(it.next, next{checker, parentPath, ref.Path, ref.ID, visit})
}
return true
})
@@ -171,10 +214,20 @@
return
}
n, it.next = it.next[topIndex], it.next[:topIndex]
+
+ if n.action == unvisit {
+ delete(it.visited, n.id)
+ continue
+ }
+
if _, ok := it.visited[n.id]; ok {
continue
}
+ // Mark as visited.
+ it.visited[n.id] = struct{}{}
+ it.pushUnvisit(n.path, n.id)
+
// Fetch the cell.
c = it.snapshot.Find(n.id)
if c == nil {
@@ -193,7 +246,7 @@
ret, expand := it.filter(n.parent, n.path)
fullPath = n.parent.AppendPath(n.path)
if expand {
- it.pushAll(checker, fullPath, c.refs)
+ it.pushVisitAll(checker, fullPath, c.refs)
}
if ret {
// Found a value.
@@ -201,8 +254,6 @@
}
}
- // Mark as visited.
- it.visited[n.id] = struct{}{}
it.entry, it.path = c.GetEntry(), fullPath
}
diff --git a/services/store/memstore/state/iterator_test.go b/services/store/memstore/state/iterator_test.go
index 344f69b..a9f3b2b 100644
--- a/services/store/memstore/state/iterator_test.go
+++ b/services/store/memstore/state/iterator_test.go
@@ -4,16 +4,48 @@
"runtime"
"testing"
+ "veyron/services/store/memstore/refs"
"veyron/services/store/memstore/state"
"veyron2/security"
"veyron2/storage"
)
+// check that the iterator produces a set of names.
+func checkAcyclicIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, filter state.IterFilter, names []string) {
+ _, file, line, _ := runtime.Caller(1)
+
+ // Construct an index of names.
+ index := map[string]bool{}
+ for _, name := range names {
+ index[name] = false
+ }
+
+ // Compute the found names.
+ for it := sn.NewIterator(id, storage.ParsePath("/"), state.ListPaths, filter); it.IsValid(); it.Next() {
+ name := it.Name()
+ if found, ok := index[name]; ok {
+ if found {
+ t.Errorf("%s(%d): duplicate name %q", file, line, name)
+ }
+ index[name] = true
+ } else {
+ t.Errorf("%s(%d): unexpected name %q", file, line, name)
+ }
+ }
+
+ // Print the not found names.
+ for name, found := range index {
+ if !found {
+ t.Errorf("%s(%d): expected: %v", file, line, name)
+ }
+ }
+}
+
// check that the iterator produces a set of names. Since entries in the store
// can have multiple names, the names are provided using a set of equivalence
// classes. The requirement is that the iterator produces exactly one name from
-// each equivalence class. Order doesn't matter.
-func checkIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, names [][]string) {
+// each equivalence class. Order doesn't matter.
+func checkUniqueObjectsIterator(t *testing.T, sn *state.MutableSnapshot, id security.PublicID, filter state.IterFilter, names [][]string) {
_, file, line, _ := runtime.Caller(1)
// Construct an index of name to equivalence class.
@@ -26,7 +58,7 @@
// Compute the found set of equivalence classes.
found := map[int]bool{}
- for it := sn.NewIterator(id, storage.ParsePath("/"), nil); it.IsValid(); it.Next() {
+ for it := sn.NewIterator(id, storage.ParsePath("/"), state.ListObjects, filter); it.IsValid(); it.Next() {
name := it.Name()
if i, ok := index[name]; ok {
if _, ok := found[i]; ok {
@@ -46,6 +78,55 @@
}
}
+// Tests that an iterator returns all non-cyclic paths that reach an object.
+func TestDuplicatePaths(t *testing.T) {
+ st := state.New(rootPublicID)
+ sn := st.MutableSnapshot()
+
+ // Add some objects
+ put(t, sn, rootPublicID, "/", "")
+ put(t, sn, rootPublicID, "/teams", "")
+ put(t, sn, rootPublicID, "/teams/cardinals", "")
+ put(t, sn, rootPublicID, "/players", "")
+ mattID := put(t, sn, rootPublicID, "/players/matt", "")
+
+ // Add some hard links
+ put(t, sn, rootPublicID, "/teams/cardinals/mvp", mattID)
+
+ checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+ "",
+ "teams",
+ "players",
+ "teams/cardinals",
+ "players/matt",
+ "teams/cardinals/mvp",
+ })
+ checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
+ {""},
+ {"teams"},
+ {"players"},
+ {"teams/cardinals"},
+ {"players/matt", "teams/cardinals/mvp"},
+ })
+
+ // Test that the iterator does not revisit objects on previously rejected paths.
+ rejected := false
+ rejectMatt := func(fullPath *refs.FullPath, path *refs.Path) (bool, bool) {
+ name := fullPath.Append(path.Suffix(1)).Name().String()
+ if !rejected && (name == "players/matt" || name == "teams/cardinals/mvp") {
+ rejected = true
+ return false, true
+ }
+ return true, true
+ }
+ checkUniqueObjectsIterator(t, sn, rootPublicID, rejectMatt, [][]string{
+ {""},
+ {"teams"},
+ {"players"},
+ {"teams/cardinals"},
+ })
+}
+
// Test that an iterator doesn't get stuck in cycles.
func TestCyclicStructure(t *testing.T) {
st := state.New(rootPublicID)
@@ -63,7 +144,17 @@
put(t, sn, rootPublicID, "/players/matt/team", cardinalsID)
put(t, sn, rootPublicID, "/teams/cardinals/mvp", mattID)
- checkIterator(t, sn, rootPublicID, [][]string{
+ checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+ "",
+ "teams",
+ "players",
+ "players/joe",
+ "players/matt",
+ "teams/cardinals/mvp",
+ "teams/cardinals",
+ "players/matt/team",
+ })
+ checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
{""},
{"teams"},
{"players"},
@@ -108,7 +199,21 @@
put(t, sn, rootPublicID, "/Users/john/shared", sharedID)
// Root gets everything.
- checkIterator(t, sn, rootPublicID, [][]string{
+ checkAcyclicIterator(t, sn, rootPublicID, nil, []string{
+ "",
+ "Users",
+ "Users/jane",
+ "Users/jane/acls",
+ "Users/jane/acls/janeRWA",
+ "Users/jane/aaa",
+ "Users/john",
+ "Users/john/acls",
+ "Users/john/acls/johnRWA",
+ "Users/john/aaa",
+ "Users/jane/shared",
+ "Users/john/shared",
+ })
+ checkUniqueObjectsIterator(t, sn, rootPublicID, nil, [][]string{
{""},
{"Users"},
{"Users/jane"},
@@ -123,7 +228,16 @@
})
// Jane sees only her names.
- checkIterator(t, sn, janePublicID, [][]string{
+ checkAcyclicIterator(t, sn, janePublicID, nil, []string{
+ "",
+ "Users",
+ "Users/jane",
+ "Users/jane/acls",
+ "Users/jane/acls/janeRWA",
+ "Users/jane/aaa",
+ "Users/jane/shared",
+ })
+ checkUniqueObjectsIterator(t, sn, janePublicID, nil, [][]string{
{""},
{"Users"},
{"Users/jane"},
@@ -134,7 +248,16 @@
})
// John sees only his names.
- checkIterator(t, sn, johnPublicID, [][]string{
+ checkAcyclicIterator(t, sn, johnPublicID, nil, []string{
+ "",
+ "Users",
+ "Users/john",
+ "Users/john/acls",
+ "Users/john/acls/johnRWA",
+ "Users/john/aaa",
+ "Users/john/shared",
+ })
+ checkUniqueObjectsIterator(t, sn, johnPublicID, nil, [][]string{
{""},
{"Users"},
{"Users/john"},
diff --git a/services/store/memstore/state/snapshot.go b/services/store/memstore/state/snapshot.go
index fd3a032..4213a55 100644
--- a/services/store/memstore/state/snapshot.go
+++ b/services/store/memstore/state/snapshot.go
@@ -12,11 +12,12 @@
)
type Snapshot interface {
- // NewIterator returns an Iterator that starts with the value at <path>. If
- // filter is given it is used to limit traversal beneath certain paths.
- // filter can be specified to limit the results of the iteration. If filter
- // is nil, all decendents of the specified path are returned.
- NewIterator(pid security.PublicID, path storage.PathName, filter IterFilter) Iterator
+ // NewIterator returns an Iterator that starts with the value at <path>.
+ // pathFilter is used to automatically limit traversal of certain paths.
+ // If filter is given, it is used to limit traversal beneath certain paths
+ // and limit the results of the iteration. If filter is nil, all decendents
+ // of the specified path are returned.
+ NewIterator(pid security.PublicID, path storage.PathName, pathFilter PathFilter, filter IterFilter) Iterator
// PathMatch returns true iff there is a name for the store value that
// matches the pathRegex.
diff --git a/services/store/memstore/watch/glob_processor_test.go b/services/store/memstore/watch/glob_processor_test.go
index 699d5f5..02f73c7 100644
--- a/services/store/memstore/watch/glob_processor_test.go
+++ b/services/store/memstore/watch/glob_processor_test.go
@@ -19,6 +19,9 @@
id1 := put(t, st, tr, "/", "val1")
id2 := put(t, st, tr, "/a", "val2")
put(t, st, tr, "/a/b", "val3")
+ id4 := put(t, st, tr, "/a/c", "val4")
+ // Test duplicate paths to the same object.
+ put(t, st, tr, "/a/d", id4)
commit(t, tr)
// Remove /a/b.
@@ -44,20 +47,26 @@
aRecursiveProcessor := createGlobProcessor(t, storage.ParsePath("/a"), "...")
aListProcessor := createGlobProcessor(t, storage.ParsePath("/a"), "*")
- // Expect initial state that contains / and /a.
+ // Expect initial state that contains /, /a, /a/c and /a/d.
logst := readState(t, log)
- changes := processState(t, rootRecursiveProcessor, logst, 2)
+ changes := processState(t, rootRecursiveProcessor, logst, 4)
watchtesting.ExpectEntryExists(t, changes, "", id1, "val1")
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
changes = processState(t, rootListProcessor, logst, 1)
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
- changes = processState(t, aRecursiveProcessor, logst, 1)
+ changes = processState(t, aRecursiveProcessor, logst, 3)
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
- processState(t, aListProcessor, logst, 0)
+ processState(t, aListProcessor, logst, 2)
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
}
func TestGlobProcessTransactionAdd(t *testing.T) {
@@ -85,25 +94,34 @@
id1 := put(t, st, tr, "/", "val1")
id2 := put(t, st, tr, "/a", "val2")
id3 := put(t, st, tr, "/a/b", "val3")
+ id4 := put(t, st, tr, "/a/c", "val4")
+ // Test duplicate paths to the same object.
+ put(t, st, tr, "/a/d", id4)
commit(t, tr)
// Expect transaction that adds /, /a and /a/b.
mus := readTransaction(t, log)
- changes := processTransaction(t, rootRecursiveProcessor, mus, 3)
+ changes := processTransaction(t, rootRecursiveProcessor, mus, 5)
watchtesting.ExpectEntryExists(t, changes, "", id1, "val1")
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
changes = processTransaction(t, rootListProcessor, mus, 1)
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
- changes = processTransaction(t, aRecursiveProcessor, mus, 2)
+ changes = processTransaction(t, aRecursiveProcessor, mus, 4)
watchtesting.ExpectEntryExists(t, changes, "a", id2, "val2")
watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
- changes = processTransaction(t, aListProcessor, mus, 1)
+ changes = processTransaction(t, aListProcessor, mus, 3)
watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
+ watchtesting.ExpectEntryExists(t, changes, "a/c", id4, "val4")
+ watchtesting.ExpectEntryExists(t, changes, "a/d", id4, "val4")
changes = processTransaction(t, bRecursiveProcessor, mus, 1)
watchtesting.ExpectEntryExists(t, changes, "a/b", id3, "val3")
diff --git a/services/store/memstore/watch/raw_processor.go b/services/store/memstore/watch/raw_processor.go
index a783caf..5c8d76e 100644
--- a/services/store/memstore/watch/raw_processor.go
+++ b/services/store/memstore/watch/raw_processor.go
@@ -67,7 +67,9 @@
// Create a change for each id in the state. In each change, the object
// exists, has no PriorVersion, has the Version of the new cell, and
// has the Value, Tags and Dir of the new cell.
- for it := sn.NewIterator(p.pid, nil, state.RecursiveFilter); it.IsValid(); it.Next() {
+ for it := sn.NewIterator(p.pid, nil,
+ state.ListObjects, state.RecursiveFilter); it.IsValid(); it.Next() {
+
entry := it.Get()
id := entry.Stat.ID
// Retrieve Value, Tags and Dir from the corresponding cell.
diff --git a/services/store/raw/service.vdl.go b/services/store/raw/service.vdl.go
index ca92493..7044eeb 100644
--- a/services/store/raw/service.vdl.go
+++ b/services/store/raw/service.vdl.go
@@ -93,14 +93,26 @@
type StoreWatchStream interface {
// Recv returns the next item in the input stream, blocking until
- // an item is available. Returns io.EOF to indicate graceful end of input.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item watch.ChangeBatch, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish blocks until the server is done and returns the positional
+ // return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -129,7 +141,7 @@
// Watch in the service interface Store.
type StoreServiceWatchStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item watch.ChangeBatch) error
}
@@ -146,21 +158,38 @@
// PutMutations in the service interface Store.
type StorePutMutationsStream interface {
- // Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // Send places the item onto the output stream, blocking if there is no
+ // buffer space available. Calls to Send after having called CloseSend
+ // or Cancel will fail. Any blocked Send calls will be unblocked upon
+ // calling Cancel.
Send(item Mutation) 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 indicates to the server that no more items will be sent;
+ // server Recv calls will receive io.EOF after all sent items. This is
+ // an optional call - it's used by streaming clients that need the
+ // server to receive the io.EOF terminator before the client calls
+ // Finish (for example, if the client needs to continue receiving items
+ // from the server after having finished sending).
+ // Calls to CloseSend after having called Cancel will fail.
+ // Like Send, CloseSend blocks when there's no buffer space available.
CloseSend() error
- // Finish closes the stream and returns the positional return values for
+ // Finish performs the equivalent of CloseSend, then blocks until the server
+ // is done, and returns the positional return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
diff --git a/services/store/typeregistryhack/init.go b/services/store/typeregistryhack/init.go
index 84a8f00..72b5216 100644
--- a/services/store/typeregistryhack/init.go
+++ b/services/store/typeregistryhack/init.go
@@ -5,10 +5,7 @@
package typeregistryhack
import (
- "veyron/services/mgmt/profile"
- "veyron2/services/mgmt/application"
-
- // Register boxes types
+ // Register boxes types.
"veyron/examples/boxes"
// Register mdb types.
_ "veyron/examples/storage/mdb/schema"
@@ -18,6 +15,12 @@
_ "veyron/examples/bank/schema"
// Register stfortune types.
_ "veyron/examples/stfortune/schema"
+ // Register profile types.
+ "veyron/services/mgmt/profile"
+ // Register application types.
+ "veyron2/services/mgmt/application"
+ // Register build types.
+ _ "veyron2/services/mgmt/build"
"veyron2/vom"
)
diff --git a/services/wspr/wsprd/lib/stream.go b/services/wspr/wsprd/lib/stream.go
deleted file mode 100644
index 062115b..0000000
--- a/services/wspr/wsprd/lib/stream.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// The set of streaming helper objects for wspr.
-
-package lib
-
-import (
- "veyron2/ipc"
-)
-
-// An interface for an asynchronous sender.
-type sender interface {
- // Similar to ipc.Stream.Send, expect that instead of
- // returning an error, w.sendError will be called.
- Send(item interface{}, w clientWriter)
-}
-
-// A message that will be passed to the writeLoop function that will
-// eventually write the message out to the stream.
-type streamMessage struct {
- // The data to put on the stream.
- data interface{}
- // The client writer that will be used to send errors.
- w clientWriter
-}
-
-// A stream that will eventually write messages to the underlying stream.
-// It isn't initialized with a stream, but rather a chan that will eventually
-// provide a stream, so that it can accept sends before the underlying stream
-// has been set up.
-type queueingStream chan *streamMessage
-
-// Creates and returns a queueing stream that will starting writing to the
-// stream provided by the ready channel. It is expected that ready will only
-// provide a single stream.
-// TODO(bjornick): allow for ready to pass an error if the stream had any issues
-// setting up.
-func startQueueingStream(ready chan ipc.Stream) queueingStream {
- s := make(queueingStream, 100)
- go s.writeLoop(ready)
- return s
-}
-
-func (q queueingStream) Send(item interface{}, w clientWriter) {
- // TODO(bjornick): Reject the message if the queue is too long.
- message := streamMessage{data: item, w: w}
- q <- &message
-}
-
-func (q queueingStream) Close() error {
- close(q)
- return nil
-}
-
-func (q queueingStream) writeLoop(ready chan ipc.Stream) {
- stream := <-ready
- for value, ok := <-q; ok; value, ok = <-q {
- if !ok {
- break
- }
- if err := stream.Send(value.data); err != nil {
- value.w.sendError(err)
- }
- }
-
- // If the stream is on the client side, then also close the stream.
- if call, ok := stream.(ipc.Call); ok {
- call.CloseSend()
- }
-}
-
-// A simple struct that wraps a stream with the sender api. It
-// will write to the stream synchronously. Any error will still
-// be written to clientWriter.
-type senderWrapper struct {
- stream ipc.Stream
-}
-
-func (s senderWrapper) Send(item interface{}, w clientWriter) {
- if err := s.stream.Send(item); err != nil {
- w.sendError(err)
- }
-}
diff --git a/services/wsprd/ipc/client/stream.go b/services/wsprd/ipc/client/stream.go
new file mode 100644
index 0000000..4237f8f
--- /dev/null
+++ b/services/wsprd/ipc/client/stream.go
@@ -0,0 +1,62 @@
+// A client stream helper.
+
+package client
+
+import (
+ "veyron/services/wsprd/lib"
+ "veyron2/ipc"
+)
+
+// A message that will be passed to the writeLoop function that will
+// eventually write the message out to the stream.
+type streamMessage struct {
+ // The data to put on the stream.
+ data interface{}
+ // The client writer that will be used to send errors.
+ w lib.ClientWriter
+}
+
+// A stream that will eventually write messages to the underlying stream.
+// It isn't initialized with a stream, but rather a chan that will eventually
+// provide a stream, so that it can accept sends before the underlying stream
+// has been set up.
+type QueueingStream chan *streamMessage
+
+// Creates and returns a queueing stream that will starting writing to the
+// stream provided by the ready channel. It is expected that ready will only
+// provide a single stream.
+// TODO(bjornick): allow for ready to pass an error if the stream had any issues
+// setting up.
+func StartQueueingStream(ready chan ipc.Stream) QueueingStream {
+ s := make(QueueingStream, 100)
+ go s.writeLoop(ready)
+ return s
+}
+
+func (q QueueingStream) Send(item interface{}, w lib.ClientWriter) {
+ // TODO(bjornick): Reject the message if the queue is too long.
+ message := streamMessage{data: item, w: w}
+ q <- &message
+}
+
+func (q QueueingStream) Close() error {
+ close(q)
+ return nil
+}
+
+func (q QueueingStream) writeLoop(ready chan ipc.Stream) {
+ stream := <-ready
+ for value, ok := <-q; ok; value, ok = <-q {
+ if !ok {
+ break
+ }
+ if err := stream.Send(value.data); err != nil {
+ value.w.Error(err)
+ }
+ }
+
+ // If the stream is on the client side, then also close the stream.
+ if call, ok := stream.(ipc.Call); ok {
+ call.CloseSend()
+ }
+}
diff --git a/services/wspr/wsprd/lib/dispatcher.go b/services/wsprd/ipc/server/dispatcher.go
similarity index 97%
rename from services/wspr/wsprd/lib/dispatcher.go
rename to services/wsprd/ipc/server/dispatcher.go
index 97b4aab..7c73701 100644
--- a/services/wspr/wsprd/lib/dispatcher.go
+++ b/services/wsprd/ipc/server/dispatcher.go
@@ -1,4 +1,4 @@
-package lib
+package server
import (
"veyron2/ipc"
diff --git a/services/wspr/wsprd/lib/invoker.go b/services/wsprd/ipc/server/invoker.go
similarity index 96%
rename from services/wspr/wsprd/lib/invoker.go
rename to services/wsprd/ipc/server/invoker.go
index a385d4d..aabafe8 100644
--- a/services/wspr/wsprd/lib/invoker.go
+++ b/services/wsprd/ipc/server/invoker.go
@@ -1,4 +1,4 @@
-package lib
+package server
import (
"veyron2/ipc"
@@ -24,7 +24,7 @@
predefinedInvokers := make(map[string]ipc.Invoker)
// Special handling for predefined "signature" method
- predefinedInvokers[signatureMethodName] = newSignatureInvoker(sig)
+ predefinedInvokers["Signature"] = newSignatureInvoker(sig)
i := &invoker{sig, invokeFunc, predefinedInvokers}
return i, nil
diff --git a/services/wspr/wsprd/lib/server.go b/services/wsprd/ipc/server/server.go
similarity index 65%
rename from services/wspr/wsprd/lib/server.go
rename to services/wsprd/ipc/server/server.go
index 8b95ea2..8b79b15 100644
--- a/services/wspr/wsprd/lib/server.go
+++ b/services/wsprd/ipc/server/server.go
@@ -1,6 +1,6 @@
// An implementation of a server for WSPR
-package lib
+package server
import (
"bytes"
@@ -8,17 +8,19 @@
"fmt"
"sync"
+ "veyron/services/wsprd/ipc/stream"
+ "veyron/services/wsprd/lib"
+ "veyron/services/wsprd/signature"
"veyron2"
"veyron2/ipc"
"veyron2/security"
"veyron2/verror"
"veyron2/vlog"
- "veyron2/vom"
)
-type flow struct {
- id int64
- writer clientWriter
+type Flow struct {
+ ID int64
+ Writer lib.ClientWriter
}
// A request from the proxy to javascript to handle an RPC
@@ -35,17 +37,23 @@
Name string
}
-type serverHelper interface {
- createNewFlow(server *server, sender sender) *flow
-
- cleanupFlow(id int64)
-
- getLogger() vlog.Logger
-
- rt() veyron2.Runtime
+// The response from the javascript server to the proxy.
+type serverRPCReply struct {
+ Results []interface{}
+ Err *verror.Standard
}
-type server struct {
+type ServerHelper interface {
+ CreateNewFlow(server *Server, sender stream.Sender) *Flow
+
+ CleanupFlow(id int64)
+
+ GetLogger() vlog.Logger
+
+ RT() veyron2.Runtime
+}
+
+type Server struct {
sync.Mutex
// The server that handles the ipc layer. Listen on this server is
@@ -61,7 +69,7 @@
// The server id.
id uint64
- helper serverHelper
+ helper ServerHelper
// The proxy to listen through.
veyronProxy string
@@ -70,15 +78,15 @@
outstandingServerRequests map[int64]chan *serverRPCReply
}
-func newServer(id uint64, veyronProxy string, helper serverHelper) (*server, error) {
- server := &server{
+func NewServer(id uint64, veyronProxy string, helper ServerHelper) (*Server, error) {
+ server := &Server{
id: id,
helper: helper,
veyronProxy: veyronProxy,
outstandingServerRequests: make(map[int64]chan *serverRPCReply),
}
var err error
- if server.server, err = helper.rt().NewServer(); err != nil {
+ if server.server, err = helper.RT().NewServer(); err != nil {
return nil, err
}
return server, nil
@@ -88,12 +96,12 @@
// communicate the result back via a channel to the caller
type remoteInvokeFunc func(methodName string, args []interface{}, call ipc.ServerCall) <-chan *serverRPCReply
-func (s *server) createRemoteInvokerFunc() remoteInvokeFunc {
+func (s *Server) createRemoteInvokerFunc() remoteInvokeFunc {
return func(methodName string, args []interface{}, call ipc.ServerCall) <-chan *serverRPCReply {
- flow := s.helper.createNewFlow(s, senderWrapper{stream: call})
+ flow := s.helper.CreateNewFlow(s, senderWrapper{stream: call})
replyChan := make(chan *serverRPCReply, 1)
s.Lock()
- s.outstandingServerRequests[flow.id] = replyChan
+ s.outstandingServerRequests[flow.ID] = replyChan
s.Unlock()
context := serverRPCRequestContext{
Suffix: call.Suffix(),
@@ -102,13 +110,12 @@
// Send a invocation request to JavaScript
message := serverRPCRequest{
ServerId: s.id,
- Method: lowercaseFirstCharacter(methodName),
+ Method: lib.LowercaseFirstCharacter(methodName),
Args: args,
Context: context,
}
- data := response{Type: responseServerRequest, Message: message}
- if err := vom.ObjToJSON(flow.writer, vom.ValueOf(data)); err != nil {
+ if err := flow.Writer.Send(lib.ResponseServerRequest, message); err != nil {
// Error in marshaling, pass the error through the channel immediately
replyChan <- &serverRPCReply{nil,
&verror.Standard{
@@ -117,49 +124,32 @@
}
return replyChan
}
- if err := flow.writer.FinishMessage(); err != nil {
- replyChan <- &serverRPCReply{nil,
- &verror.Standard{
- ID: verror.Internal,
- Msg: fmt.Sprintf("WSPR: error finishing message: %v", err)},
- }
- return replyChan
- }
- s.helper.getLogger().VI(3).Infof("request received to call method %q on "+
+ s.helper.GetLogger().VI(3).Infof("request received to call method %q on "+
"JavaScript server with args %v, MessageId %d was assigned.",
- methodName, args, flow.id)
+ methodName, args, flow.ID)
- go proxyStream(call, flow.writer, s.helper.getLogger())
+ go proxyStream(call, flow.Writer, s.helper.GetLogger())
return replyChan
}
}
-func proxyStream(stream ipc.Stream, w clientWriter, logger vlog.Logger) {
+func proxyStream(stream ipc.Stream, w lib.ClientWriter, logger vlog.Logger) {
var item interface{}
for err := stream.Recv(&item); err == nil; err = stream.Recv(&item) {
- data := response{Type: responseStream, Message: item}
- if err := vom.ObjToJSON(w, vom.ValueOf(data)); err != nil {
- w.sendError(verror.Internalf("error marshalling stream: %v:", err))
- return
- }
- if err := w.FinishMessage(); err != nil {
- logger.Error("WSPR: error finishing message", err)
+ if err := w.Send(lib.ResponseStream, item); err != nil {
+ w.Error(verror.Internalf("error marshalling stream: %v:", err))
return
}
}
- if err := vom.ObjToJSON(w, vom.ValueOf(response{Type: responseStreamClose})); err != nil {
- w.sendError(verror.Internalf("error closing stream: %v:", err))
- return
- }
- if err := w.FinishMessage(); err != nil {
- logger.Error("WSPR: error finishing message", err)
+ if err := w.Send(lib.ResponseStreamClose, nil); err != nil {
+ w.Error(verror.Internalf("error closing stream: %v:", err))
return
}
}
-func (s *server) serve(name string, sig JSONServiceSignature) (string, error) {
+func (s *Server) Serve(name string, sig signature.JSONServiceSignature) (string, error) {
s.Lock()
defer s.Unlock()
@@ -192,17 +182,17 @@
if err := s.server.Serve(name, s.dispatcher); err != nil {
return "", err
}
- s.helper.getLogger().VI(1).Infof("endpoint is %s", s.endpoint)
+ s.helper.GetLogger().VI(1).Infof("endpoint is %s", s.endpoint)
return s.endpoint, nil
}
-func (s *server) handleServerResponse(id int64, data string) {
+func (s *Server) HandleServerResponse(id int64, data string) {
s.Lock()
ch := s.outstandingServerRequests[id]
delete(s.outstandingServerRequests, id)
s.Unlock()
if ch == nil {
- s.helper.getLogger().Errorf("unexpected result from JavaScript. No channel "+
+ s.helper.GetLogger().Errorf("unexpected result from JavaScript. No channel "+
"for MessageId: %d exists. Ignoring the results.", id)
//Ignore unknown responses that don't belong to any channel
return
@@ -218,13 +208,13 @@
serverReply = serverRPCReply{nil, &err}
}
- s.helper.getLogger().VI(3).Infof("response received from JavaScript server for "+
+ s.helper.GetLogger().VI(3).Infof("response received from JavaScript server for "+
"MessageId %d with result %v", id, serverReply)
- s.helper.cleanupFlow(id)
+ s.helper.CleanupFlow(id)
ch <- &serverReply
}
-func (s *server) Stop() {
+func (s *Server) Stop() {
result := serverRPCReply{
Results: []interface{}{nil},
Err: &verror.Standard{
diff --git a/services/wspr/wsprd/lib/signature_invoker.go b/services/wsprd/ipc/server/signature_invoker.go
similarity index 97%
rename from services/wspr/wsprd/lib/signature_invoker.go
rename to services/wsprd/ipc/server/signature_invoker.go
index 56026e2..6384510 100644
--- a/services/wspr/wsprd/lib/signature_invoker.go
+++ b/services/wsprd/ipc/server/signature_invoker.go
@@ -1,4 +1,4 @@
-package lib
+package server
import (
"veyron2/ipc"
diff --git a/services/wsprd/ipc/server/stream.go b/services/wsprd/ipc/server/stream.go
new file mode 100644
index 0000000..03b39e2
--- /dev/null
+++ b/services/wsprd/ipc/server/stream.go
@@ -0,0 +1,19 @@
+package server
+
+import (
+ "veyron/services/wsprd/lib"
+ "veyron2/ipc"
+)
+
+// A simple struct that wraps a stream with the sender api. It
+// will write to the stream synchronously. Any error will still
+// be written to clientWriter.
+type senderWrapper struct {
+ stream ipc.Stream
+}
+
+func (s senderWrapper) Send(item interface{}, w lib.ClientWriter) {
+ if err := s.stream.Send(item); err != nil {
+ w.Error(err)
+ }
+}
diff --git a/services/wsprd/ipc/stream/stream.go b/services/wsprd/ipc/stream/stream.go
new file mode 100644
index 0000000..57ef1b3
--- /dev/null
+++ b/services/wsprd/ipc/stream/stream.go
@@ -0,0 +1,14 @@
+// The set of streaming helper objects for wspr.
+
+package stream
+
+import (
+ "veyron/services/wsprd/lib"
+)
+
+// An interface for an asynchronous sender.
+type Sender interface {
+ // Similar to ipc.Stream.Send, expect that instead of
+ // returning an error, w.sendError will be called.
+ Send(item interface{}, w lib.ClientWriter)
+}
diff --git a/services/wspr/wsprd/lib/cache.go b/services/wsprd/lib/cache.go
similarity index 100%
rename from services/wspr/wsprd/lib/cache.go
rename to services/wsprd/lib/cache.go
diff --git a/services/wspr/wsprd/lib/case.go b/services/wsprd/lib/case.go
similarity index 67%
rename from services/wspr/wsprd/lib/case.go
rename to services/wsprd/lib/case.go
index b440561..e6ec0d0 100644
--- a/services/wspr/wsprd/lib/case.go
+++ b/services/wsprd/lib/case.go
@@ -2,14 +2,14 @@
import "unicode"
-func lowercaseFirstCharacter(s string) string {
+func LowercaseFirstCharacter(s string) string {
for _, r := range s {
return string(unicode.ToLower(r)) + s[1:]
}
return ""
}
-func uppercaseFirstCharacter(s string) string {
+func UppercaseFirstCharacter(s string) string {
for _, r := range s {
return string(unicode.ToUpper(r)) + s[1:]
}
diff --git a/services/wspr/wsprd/lib/remove_this.go b/services/wsprd/lib/remove_this.go
similarity index 100%
rename from services/wspr/wsprd/lib/remove_this.go
rename to services/wsprd/lib/remove_this.go
diff --git a/services/wspr/wsprd/lib/signature_manager.go b/services/wsprd/lib/signature_manager.go
similarity index 84%
rename from services/wspr/wsprd/lib/signature_manager.go
rename to services/wsprd/lib/signature_manager.go
index 642dfe8..18a76a8 100644
--- a/services/wspr/wsprd/lib/signature_manager.go
+++ b/services/wsprd/lib/signature_manager.go
@@ -8,9 +8,8 @@
"veyron2/ipc"
)
-// NewSignatureManager creates and initialized a new Signature Manager
-func newSignatureManager() *signatureManager {
- return &signatureManager{cache: make(map[string]*cacheEntry)}
+type SignatureManager interface {
+ Signature(ctx context.T, name string, client ipc.Client) (*ipc.ServiceSignature, error)
}
// signatureManager can be used to discover the signature of a remote service
@@ -24,6 +23,11 @@
cache map[string]*cacheEntry
}
+// NewSignatureManager creates and initialized a new Signature Manager
+func NewSignatureManager() SignatureManager {
+ return &signatureManager{cache: make(map[string]*cacheEntry)}
+}
+
const (
// ttl from the last-accessed time.
ttl = time.Duration(time.Hour)
@@ -41,7 +45,7 @@
// signature uses the given client to fetch the signature for the given service name.
// It locks until it fetches the service signature from the remote server, if not a cache hit.
-func (sm *signatureManager) signature(ctx context.T, name string, client ipc.Client) (*ipc.ServiceSignature, error) {
+func (sm *signatureManager) Signature(ctx context.T, name string, client ipc.Client) (*ipc.ServiceSignature, error) {
sm.Lock()
defer sm.Unlock()
@@ -51,7 +55,7 @@
}
// cache expired or not found, fetch it from the remote server
- signatureCall, err := client.StartCall(ctx, name, signatureMethodName, []interface{}{})
+ signatureCall, err := client.StartCall(ctx, name, "Signature", []interface{}{})
if err != nil {
return nil, err
}
diff --git a/services/wspr/wsprd/lib/signature_manager_test.go b/services/wsprd/lib/signature_manager_test.go
similarity index 81%
rename from services/wspr/wsprd/lib/signature_manager_test.go
rename to services/wsprd/lib/signature_manager_test.go
index ac60393..f0caf01 100644
--- a/services/wspr/wsprd/lib/signature_manager_test.go
+++ b/services/wsprd/lib/signature_manager_test.go
@@ -15,6 +15,10 @@
name = "/veyron/name"
)
+func init() {
+ rt.Init()
+}
+
func expectedSignature() ipc.ServiceSignature {
return ipc.ServiceSignature{
Methods: make(map[string]ipc.MethodSignature),
@@ -30,7 +34,7 @@
func client() *mocks_ipc.SimpleMockClient {
return mocks_ipc.NewSimpleClient(
map[string][]interface{}{
- signatureMethodName: []interface{}{expectedSignature(), nil},
+ "Signature": []interface{}{expectedSignature(), nil},
},
)
}
@@ -75,8 +79,8 @@
}
func TestFetching(t *testing.T) {
- sm := newSignatureManager()
- got, err := sm.signature(rt.R().NewContext(), name, client())
+ sm := NewSignatureManager()
+ got, err := sm.Signature(rt.R().NewContext(), name, client())
if err != nil {
t.Errorf(`Did not expect an error but got %v`, err)
return
@@ -86,8 +90,8 @@
}
func TestThatCachedAfterFetching(t *testing.T) {
- sm := newSignatureManager()
- sig, _ := sm.signature(rt.R().NewContext(), name, client())
+ sm := NewSignatureManager().(*signatureManager)
+ sig, _ := sm.Signature(rt.R().NewContext(), name, client())
cache, ok := sm.cache[name]
if !ok {
t.Errorf(`Signature manager did not cache the results`)
@@ -98,30 +102,30 @@
func TestThatCacheIsUsed(t *testing.T) {
client := client()
- sm := newSignatureManager()
+ sm := NewSignatureManager()
// call twice
- sm.signature(rt.R().NewContext(), name, client)
- sm.signature(rt.R().NewContext(), name, client)
+ sm.Signature(rt.R().NewContext(), name, client)
+ sm.Signature(rt.R().NewContext(), name, client)
// expect number of calls to Signature method of client to still be 1 since cache
// should have been used despite the second call
- if client.TimesCalled(signatureMethodName) != 1 {
+ if client.TimesCalled("Signature") != 1 {
t.Errorf("Signature cache was not used for the second call")
}
}
func TestThatLastAccessedGetUpdated(t *testing.T) {
client := client()
- sm := newSignatureManager()
- sm.signature(rt.R().NewContext(), name, client)
+ sm := NewSignatureManager().(*signatureManager)
+ sm.Signature(rt.R().NewContext(), name, client)
// make last accessed be in the past to account for the fact that
// two consecutive calls to time.Now() can return identical values.
sm.cache[name].lastAccessed = sm.cache[name].lastAccessed.Add(-ttl / 2)
prevAccess := sm.cache[name].lastAccessed
// access again
- sm.signature(rt.R().NewContext(), name, client)
+ sm.Signature(rt.R().NewContext(), name, client)
newAccess := sm.cache[name].lastAccessed
if !newAccess.After(prevAccess) {
@@ -131,17 +135,17 @@
func TestThatTTLExpires(t *testing.T) {
client := client()
- sm := newSignatureManager()
- sm.signature(rt.R().NewContext(), name, client)
+ sm := NewSignatureManager().(*signatureManager)
+ sm.Signature(rt.R().NewContext(), name, client)
// make last accessed go over the ttl
sm.cache[name].lastAccessed = sm.cache[name].lastAccessed.Add(-2 * ttl)
// make a second call
- sm.signature(rt.R().NewContext(), name, client)
+ sm.Signature(rt.R().NewContext(), name, client)
// expect number of calls to Signature method of client to be 2 since cache should have expired
- if client.TimesCalled(signatureMethodName) != 2 {
+ if client.TimesCalled("Signature") != 2 {
t.Errorf("Cache was still used but TTL had passed. It should have been fetched again")
}
}
diff --git a/services/wsprd/lib/writer.go b/services/wsprd/lib/writer.go
new file mode 100644
index 0000000..ae18996
--- /dev/null
+++ b/services/wsprd/lib/writer.go
@@ -0,0 +1,19 @@
+package lib
+
+type ResponseType int
+
+const (
+ ResponseFinal ResponseType = 0
+ ResponseStream = 1
+ ResponseError = 2
+ ResponseServerRequest = 3
+ ResponseStreamClose = 4
+)
+
+// This is basically an io.Writer interface, that allows passing error message
+// strings. This is how the proxy will talk to the javascript/java clients.
+type ClientWriter interface {
+ Send(messageType ResponseType, data interface{}) error
+
+ Error(err error)
+}
diff --git a/services/wspr/wsprd/lib/identity.go b/services/wsprd/security/identity.go
similarity index 99%
rename from services/wspr/wsprd/lib/identity.go
rename to services/wsprd/security/identity.go
index 823d4d8..f72ad24 100644
--- a/services/wspr/wsprd/lib/identity.go
+++ b/services/wsprd/security/identity.go
@@ -10,8 +10,7 @@
// information, but not the private keys for each app.
// TODO(bjornick,ataly,ashankar): Have all the accounts share the same private key which will be stored
// in a TPM, so no private key gets serialized to disk.
-
-package lib
+package security
import (
"crypto/ecdsa"
diff --git a/services/wspr/wsprd/lib/identity_test.go b/services/wsprd/security/identity_test.go
similarity index 99%
rename from services/wspr/wsprd/lib/identity_test.go
rename to services/wsprd/security/identity_test.go
index b7ccbad..9b89999 100644
--- a/services/wspr/wsprd/lib/identity_test.go
+++ b/services/wsprd/security/identity_test.go
@@ -1,4 +1,4 @@
-package lib
+package security
import (
"bytes"
diff --git a/services/wspr/wsprd/lib/signature.go b/services/wsprd/signature/signature.go
similarity index 90%
rename from services/wspr/wsprd/lib/signature.go
rename to services/wsprd/signature/signature.go
index 54dd136..0185658 100644
--- a/services/wspr/wsprd/lib/signature.go
+++ b/services/wsprd/signature/signature.go
@@ -1,16 +1,12 @@
-package lib
+package signature
import (
+ "veyron/services/wsprd/lib"
"veyron2/ipc"
"veyron2/vdl/vdlutil"
"veyron2/wiretype"
)
-const (
- // agreed-upon name of the signature method that's available on all services
- signatureMethodName = "Signature"
-)
-
var (
anydataType = wiretype.NamedPrimitiveType{
Name: "veyron2/vdlutil.AnyData",
@@ -49,7 +45,7 @@
jmethSig.InArgs[i] = inarg.Name
}
- jsig[lowercaseFirstCharacter(name)] = jmethSig
+ jsig[lib.LowercaseFirstCharacter(name)] = jmethSig
}
return jsig
@@ -88,7 +84,7 @@
ms.OutStream = anydataTypeID
}
- ss.Methods[uppercaseFirstCharacter(name)] = ms
+ ss.Methods[lib.UppercaseFirstCharacter(name)] = ms
}
ss.TypeDefs = []vdlutil.Any{anydataType, errType}
diff --git a/services/wspr/wsprd/lib/signature_test.go b/services/wsprd/signature/signature_test.go
similarity index 98%
rename from services/wspr/wsprd/lib/signature_test.go
rename to services/wsprd/signature/signature_test.go
index e8e1f4f..250acf5 100644
--- a/services/wspr/wsprd/lib/signature_test.go
+++ b/services/wsprd/signature/signature_test.go
@@ -1,4 +1,4 @@
-package lib
+package signature
import (
"reflect"
diff --git a/services/wspr/wsprd/wspr.go b/services/wsprd/wspr.go
similarity index 81%
rename from services/wspr/wsprd/wspr.go
rename to services/wsprd/wspr.go
index ef4977d..4a01ea0 100644
--- a/services/wspr/wsprd/wspr.go
+++ b/services/wsprd/wspr.go
@@ -4,7 +4,7 @@
"flag"
"veyron/lib/signals"
- "veyron/services/wspr/wsprd/lib"
+ "veyron/services/wsprd/wspr"
)
func main() {
@@ -12,7 +12,7 @@
veyronProxy := flag.String("vproxy", "", "The endpoint for the veyron proxy to publish on. This must be set")
flag.Parse()
- proxy := lib.NewWSPR(*port, *veyronProxy)
+ proxy := wspr.NewWSPR(*port, *veyronProxy)
defer proxy.Shutdown()
go func() {
proxy.Run()
diff --git a/services/wspr/wsprd/lib/writer.go b/services/wsprd/wspr/writer.go
similarity index 60%
rename from services/wspr/wsprd/lib/writer.go
rename to services/wsprd/wspr/writer.go
index 11fd44c..4c85ea0 100644
--- a/services/wspr/wsprd/lib/writer.go
+++ b/services/wsprd/wspr/writer.go
@@ -1,10 +1,13 @@
-package lib
+package wspr
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
+
+ "veyron/services/wsprd/lib"
+
"veyron2/verror"
"veyron2/vlog"
"veyron2/vom"
@@ -12,30 +15,41 @@
"github.com/gorilla/websocket"
)
-// This is basically an io.Writer interface, that allows passing error message
-// strings. This is how the proxy will talk to the javascript/java clients.
-type clientWriter interface {
- Write(p []byte) (int, error)
-
- sendError(err error)
-
- FinishMessage() error
+// Wraps a response to the proxy client and adds a message type.
+type response struct {
+ Type lib.ResponseType
+ Message interface{}
}
// Implements clientWriter interface for sending messages over websockets.
type websocketWriter struct {
ws *websocket.Conn
- buf bytes.Buffer
logger vlog.Logger
id int64
}
-func (w *websocketWriter) Write(p []byte) (int, error) {
- w.buf.Write(p)
- return len(p), nil
+func (w *websocketWriter) Send(messageType lib.ResponseType, data interface{}) error {
+ var buf bytes.Buffer
+ if err := vom.ObjToJSON(&buf, vom.ValueOf(response{Type: messageType, Message: data})); err != nil {
+ w.logger.Error("Failed to marshal with", err)
+ return err
+ }
+
+ wc, err := w.ws.NextWriter(websocket.TextMessage)
+ if err != nil {
+ w.logger.Error("Failed to get a writer from the websocket", err)
+ return err
+ }
+ if err := vom.ObjToJSON(wc, vom.ValueOf(websocketMessage{Id: w.id, Data: buf.String()})); err != nil {
+ w.logger.Error("Failed to write the message", err)
+ return err
+ }
+ wc.Close()
+
+ return nil
}
-func (w *websocketWriter) sendError(err error) {
+func (w *websocketWriter) Error(err error) {
verr := verror.ToStandard(err)
// Also log the error but write internal errors at a more severe log level
@@ -56,26 +70,5 @@
Msg: verr.Error(),
}
- w.buf.Reset()
- if err := vom.ObjToJSON(&w.buf, vom.ValueOf(response{Type: responseError, Message: errMsg})); err != nil {
- w.logger.Error("Failed to marshal with", err)
- return
- }
- if err := w.FinishMessage(); err != nil {
- w.logger.Error("WSPR: error finishing message: ", err)
- return
- }
-}
-
-func (w *websocketWriter) FinishMessage() error {
- wc, err := w.ws.NextWriter(websocket.TextMessage)
- if err != nil {
- return err
- }
- if err := vom.ObjToJSON(wc, vom.ValueOf(websocketMessage{Id: w.id, Data: w.buf.String()})); err != nil {
- return err
- }
- wc.Close()
- w.buf.Reset()
- return nil
+ w.Send(lib.ResponseError, errMsg)
}
diff --git a/services/wspr/wsprd/lib/wspr.go b/services/wsprd/wspr/wspr.go
similarity index 80%
rename from services/wspr/wsprd/lib/wspr.go
rename to services/wsprd/wspr/wspr.go
index b48a310..81e31e9 100644
--- a/services/wspr/wsprd/lib/wspr.go
+++ b/services/wsprd/wspr/wspr.go
@@ -13,7 +13,7 @@
// "IsStreaming" : true/false
// }
//
-package lib
+package wspr
import (
"bytes"
@@ -31,6 +31,11 @@
"sync"
"time"
+ "veyron/services/wsprd/ipc/client"
+ "veyron/services/wsprd/ipc/server"
+ "veyron/services/wsprd/ipc/stream"
+ "veyron/services/wsprd/lib"
+ "veyron/services/wsprd/signature"
"veyron2"
"veyron2/ipc"
"veyron2/rt"
@@ -55,29 +60,13 @@
type WSPR struct {
tlsCert *tls.Certificate
- clientCache *ClientCache
+ clientCache *lib.ClientCache
rt veyron2.Runtime
logger vlog.Logger
port int
veyronProxyEP string
}
-type responseType int
-
-const (
- responseFinal responseType = 0
- responseStream = 1
- responseError = 2
- responseServerRequest = 3
- responseStreamClose = 4
-)
-
-// Wraps a response to the proxy client and adds a message type.
-type response struct {
- Type responseType
- Message interface{}
-}
-
var logger vlog.Logger
// The type of message sent by the JS client to the wspr.
@@ -139,17 +128,11 @@
type serveRequest struct {
Name string
ServerId uint64
- Service JSONServiceSignature
-}
-
-// The response from the javascript server to the proxy.
-type serverRPCReply struct {
- Results []interface{}
- Err *verror.Standard
+ Service signature.JSONServiceSignature
}
// finishCall waits for the call to finish and write out the response to w.
-func (wsp *websocketPipe) finishCall(w clientWriter, clientCall ipc.Call, msg *veyronRPC) {
+func (wsp *websocketPipe) finishCall(w lib.ClientWriter, clientCall ipc.Call, msg *veyronRPC) {
if msg.IsStreaming {
for {
var item interface{}
@@ -157,24 +140,16 @@
if err == io.EOF {
break
}
- w.sendError(err) // Send streaming error as is
+ w.Error(err) // Send streaming error as is
return
}
- data := &response{Type: responseStream, Message: item}
- if err := vom.ObjToJSON(w, vom.ValueOf(data)); err != nil {
- w.sendError(verror.Internalf("unable to marshal: %v", item))
- continue
- }
- if err := w.FinishMessage(); err != nil {
- wsp.ctx.logger.Error("WSPR: error finishing message: ", err)
+ if err := w.Send(lib.ResponseStream, item); err != nil {
+ w.Error(verror.Internalf("unable to marshal: %v", item))
}
}
- if err := vom.ObjToJSON(w, vom.ValueOf(response{Type: responseStreamClose})); err != nil {
- w.sendError(verror.Internalf("unable to marshal close stream message"))
- }
- if err := w.FinishMessage(); err != nil {
- wsp.ctx.logger.Error("WSPR: error finishing message: ", err)
+ if err := w.Send(lib.ResponseStreamClose, nil); err != nil {
+ w.Error(verror.Internalf("unable to marshal close stream message"))
}
}
@@ -186,30 +161,23 @@
}
if err := clientCall.Finish(resultptrs...); err != nil {
// return the call system error as is
- w.sendError(err)
+ w.Error(err)
return
}
// for now we assume last out argument is always error
if len(results) < 1 {
- w.sendError(verror.Internalf("client call did not return any results"))
+ w.Error(verror.Internalf("client call did not return any results"))
return
}
if err, ok := results[len(results)-1].(error); ok {
// return the call application error as is
- w.sendError(err)
+ w.Error(err)
return
}
- data := response{Type: responseFinal, Message: results[0 : len(results)-1]}
- if err := vom.ObjToJSON(w, vom.ValueOf(data)); err != nil {
- w.sendError(verror.Internalf("error marshalling results: %v", err))
- return
- }
-
- if err := w.FinishMessage(); err != nil {
- wsp.ctx.logger.Error("WSPR: error finishing message: ", err)
- return
+ if err := w.Send(lib.ResponseFinal, results[0:len(results)-1]); err != nil {
+ w.Error(verror.Internalf("error marshalling results: %v", err))
}
}
@@ -219,7 +187,7 @@
var err error
if client == nil {
// TODO(bjornick): Use the identity to create the client.
- client, err = ctx.rt.NewClient()
+ client, err = ctx.rt.NewClient(veyron2.CallTimeout(ipc.NoTimeout))
if err != nil {
return nil, fmt.Errorf("error creating client: %v", err)
}
@@ -229,16 +197,17 @@
return client, nil
}
-func (ctx WSPR) startVeyronRequest(w clientWriter, msg *veyronRPC) (ipc.Call, error) {
+func (ctx WSPR) startVeyronRequest(w lib.ClientWriter, msg *veyronRPC) (ipc.Call, error) {
// Issue request to the endpoint.
client, err := ctx.newClient(msg.PrivateId)
if err != nil {
return nil, err
}
- clientCall, err := client.StartCall(ctx.rt.TODOContext(), msg.Name, uppercaseFirstCharacter(msg.Method), msg.InArgs)
+ methodName := lib.UppercaseFirstCharacter(msg.Method)
+ clientCall, err := client.StartCall(ctx.rt.TODOContext(), msg.Name, methodName, msg.InArgs)
if err != nil {
- return nil, fmt.Errorf("error starting call (name: %v, method: %v, args: %v): %v", msg.Name, uppercaseFirstCharacter(msg.Method), msg.InArgs, err)
+ return nil, fmt.Errorf("error starting call (name: %v, method: %v, args: %v): %v", msg.Name, methodName, msg.InArgs, err)
}
return clientCall, nil
@@ -311,7 +280,7 @@
}
type outstandingStream struct {
- stream sender
+ stream stream.Sender
inType vom.Type
}
@@ -329,43 +298,43 @@
outstandingStreams map[int64]outstandingStream
// Maps flowids to the server that owns them.
- flowMap map[int64]*server
+ flowMap map[int64]*server.Server
// A manager that handles fetching and caching signature of remote services
- signatureManager *signatureManager
+ signatureManager lib.SignatureManager
// We maintain multiple Veyron server per websocket pipe for serving JavaScript
// services.
- servers map[uint64]*server
+ servers map[uint64]*server.Server
// Creates a client writer for a given flow. This is a member so that tests can override
// the default implementation.
- writerCreator func(id int64) clientWriter
+ writerCreator func(id int64) lib.ClientWriter
}
// Implements the serverHelper interface
-func (wsp *websocketPipe) createNewFlow(server *server, stream sender) *flow {
+func (wsp *websocketPipe) CreateNewFlow(s *server.Server, stream stream.Sender) *server.Flow {
wsp.Lock()
defer wsp.Unlock()
id := wsp.lastGeneratedId
wsp.lastGeneratedId += 2
- wsp.flowMap[id] = server
+ wsp.flowMap[id] = s
wsp.outstandingStreams[id] = outstandingStream{stream, vom_wiretype.Type{ID: 1}}
- return &flow{id: id, writer: wsp.writerCreator(id)}
+ return &server.Flow{ID: id, Writer: wsp.writerCreator(id)}
}
-func (wsp *websocketPipe) cleanupFlow(id int64) {
+func (wsp *websocketPipe) CleanupFlow(id int64) {
wsp.Lock()
defer wsp.Unlock()
delete(wsp.outstandingStreams, id)
delete(wsp.flowMap, id)
}
-func (wsp *websocketPipe) getLogger() vlog.Logger {
+func (wsp *websocketPipe) GetLogger() vlog.Logger {
return wsp.ctx.logger
}
-func (wsp *websocketPipe) rt() veyron2.Runtime {
+func (wsp *websocketPipe) RT() veyron2.Runtime {
return wsp.ctx.rt
}
@@ -385,13 +354,13 @@
}
func (wsp *websocketPipe) setup() {
- wsp.signatureManager = newSignatureManager()
+ wsp.signatureManager = lib.NewSignatureManager()
wsp.outstandingStreams = make(map[int64]outstandingStream)
- wsp.flowMap = make(map[int64]*server)
- wsp.servers = make(map[uint64]*server)
+ wsp.flowMap = make(map[int64]*server.Server)
+ wsp.servers = make(map[uint64]*server.Server)
if wsp.writerCreator == nil {
- wsp.writerCreator = func(id int64) clientWriter {
+ wsp.writerCreator = func(id int64) lib.ClientWriter {
return &websocketWriter{ws: wsp.ws, id: id, logger: wsp.ctx.logger}
}
}
@@ -455,12 +424,13 @@
return nil
}
-func (wsp *websocketPipe) sendParsedMessageOnStream(id int64, msg interface{}, w clientWriter) {
+func (wsp *websocketPipe) sendParsedMessageOnStream(id int64, msg interface{}, w lib.ClientWriter) {
wsp.Lock()
defer wsp.Unlock()
stream := wsp.outstandingStreams[id].stream
if stream == nil {
- w.sendError(fmt.Errorf("unknown stream"))
+ w.Error(fmt.Errorf("unknown stream"))
+ return
}
stream.Send(msg, w)
@@ -468,7 +438,7 @@
}
// sendOnStream writes data on id's stream. Returns an error if the send failed.
-func (wsp *websocketPipe) sendOnStream(id int64, data string, w clientWriter) {
+func (wsp *websocketPipe) sendOnStream(id int64, data string, w lib.ClientWriter) {
wsp.Lock()
typ := wsp.outstandingStreams[id].inType
wsp.Unlock()
@@ -484,12 +454,12 @@
wsp.sendParsedMessageOnStream(id, payload, w)
}
-func (wsp *websocketPipe) sendVeyronRequest(id int64, veyronMsg *veyronRPC, w clientWriter, signal chan ipc.Stream) {
+func (wsp *websocketPipe) sendVeyronRequest(id int64, veyronMsg *veyronRPC, w lib.ClientWriter, signal chan ipc.Stream) {
// We have to make the start call synchronous so we can make sure that we populate
// the call map before we can handle a recieve call.
call, err := wsp.ctx.startVeyronRequest(w, veyronMsg)
if err != nil {
- w.sendError(verror.Internalf("can't start Veyron Request: %v", err))
+ w.Error(verror.Internalf("can't start Veyron Request: %v", err))
return
}
@@ -509,7 +479,7 @@
func (wsp *websocketPipe) handleVeyronRequest(id int64, data string, w *websocketWriter) {
veyronMsg, inStreamType, err := wsp.parseVeyronRequest(bytes.NewBufferString(data))
if err != nil {
- w.sendError(verror.Internalf("can't parse Veyron Request: %v", err))
+ w.Error(verror.Internalf("can't parse Veyron Request: %v", err))
return
}
@@ -524,7 +494,7 @@
if veyronMsg.IsStreaming {
signal = make(chan ipc.Stream)
wsp.outstandingStreams[id] = outstandingStream{
- stream: startQueueingStream(signal),
+ stream: client.StartQueueingStream(signal),
inType: inStreamType,
}
}
@@ -540,9 +510,9 @@
return
}
- var call queueingStream
+ var call client.QueueingStream
var ok bool
- if call, ok = stream.(queueingStream); !ok {
+ if call, ok = stream.(client.QueueingStream); !ok {
wsp.ctx.logger.Errorf("can't close server stream: %v", id)
return
}
@@ -601,19 +571,19 @@
case websocketSignatureRequest:
go wsp.handleSignatureRequest(msg.Data, ww)
default:
- ww.sendError(verror.Unknownf("unknown message type: %v", msg.Type))
+ ww.Error(verror.Unknownf("unknown message type: %v", msg.Type))
}
}
wsp.cleanup()
}
-func (wsp *websocketPipe) maybeCreateServer(serverId uint64) (*server, error) {
+func (wsp *websocketPipe) maybeCreateServer(serverId uint64) (*server.Server, error) {
wsp.Lock()
defer wsp.Unlock()
if server, ok := wsp.servers[serverId]; ok {
return server, nil
}
- server, err := newServer(serverId, wsp.ctx.veyronProxyEP, wsp)
+ server, err := server.NewServer(serverId, wsp.ctx.veyronProxyEP, wsp)
if err != nil {
return nil, err
}
@@ -634,29 +604,23 @@
server.Stop()
}
-func (wsp *websocketPipe) serve(serveRequest serveRequest, w clientWriter) {
+func (wsp *websocketPipe) serve(serveRequest serveRequest, w lib.ClientWriter) {
// Create a server for the websocket pipe, if it does not exist already
server, err := wsp.maybeCreateServer(serveRequest.ServerId)
if err != nil {
- w.sendError(verror.Internalf("error creating server: %v", err))
+ w.Error(verror.Internalf("error creating server: %v", err))
}
wsp.ctx.logger.VI(2).Infof("serving under name: %q", serveRequest.Name)
- endpoint, err := server.serve(serveRequest.Name, serveRequest.Service)
+ endpoint, err := server.Serve(serveRequest.Name, serveRequest.Service)
if err != nil {
- w.sendError(verror.Internalf("error serving service: %v", err))
+ w.Error(verror.Internalf("error serving service: %v", err))
return
}
// Send the endpoint back
- endpointData := response{Type: responseFinal, Message: endpoint}
- if err := vom.ObjToJSON(w, vom.ValueOf(endpointData)); err != nil {
- w.sendError(verror.Internalf("error marshalling results: %v", err))
- return
- }
-
- if err := w.FinishMessage(); err != nil {
- wsp.ctx.logger.Error("WSPR: error finishing message: ", err)
+ if err := w.Send(lib.ResponseFinal, endpoint); err != nil {
+ w.Error(verror.Internalf("error marshalling results: %v", err))
return
}
}
@@ -668,7 +632,7 @@
var serveRequest serveRequest
decoder := json.NewDecoder(bytes.NewBufferString(data))
if err := decoder.Decode(&serveRequest); err != nil {
- w.sendError(verror.Internalf("can't unmarshal JSONMessage: %v", err))
+ w.Error(verror.Internalf("can't unmarshal JSONMessage: %v", err))
return
}
wsp.serve(serveRequest, w)
@@ -680,19 +644,17 @@
var serverId uint64
decoder := json.NewDecoder(bytes.NewBufferString(data))
if err := decoder.Decode(&serverId); err != nil {
- w.sendError(verror.Internalf("can't unmarshal JSONMessage: %v", err))
+ w.Error(verror.Internalf("can't unmarshal JSONMessage: %v", err))
return
}
wsp.removeServer(serverId)
// Send true to indicate stop has finished
- result := response{Type: responseFinal, Message: true}
- if err := vom.ObjToJSON(w, vom.ValueOf(result)); err != nil {
- w.sendError(verror.Internalf("error marshalling results: %v", err))
+ if err := w.Send(lib.ResponseFinal, true); err != nil {
+ w.Error(verror.Internalf("error marshalling results: %v", err))
return
}
- w.FinishMessage()
}
// handleServerResponse handles the completion of outstanding calls to JavaScript services
@@ -707,7 +669,7 @@
//Ignore unknown responses that don't belong to any channel
return
}
- server.handleServerResponse(id, data)
+ server.HandleServerResponse(id, data)
}
// parseVeyronRequest parses a json rpc request into a veyronRPC object.
@@ -725,12 +687,12 @@
// Fetch and adapt signature from the SignatureManager
ctx := wsp.ctx.rt.TODOContext()
- sig, err := wsp.signatureManager.signature(ctx, tempMsg.Name, client)
+ sig, err := wsp.signatureManager.Signature(ctx, tempMsg.Name, client)
if err != nil {
return nil, nil, verror.Internalf("error getting service signature for %s: %v", tempMsg.Name, err)
}
- methName := uppercaseFirstCharacter(tempMsg.Method)
+ methName := lib.UppercaseFirstCharacter(tempMsg.Method)
methSig, ok := sig.Methods[methName]
if !ok {
return nil, nil, fmt.Errorf("Method not found in signature: %v (full sig: %v)", methName, sig)
@@ -777,7 +739,7 @@
PrivateId string
}
-func (wsp *websocketPipe) getSignature(name string, privateId string) (JSONServiceSignature, error) {
+func (wsp *websocketPipe) getSignature(name string, privateId string) (signature.JSONServiceSignature, error) {
client, err := wsp.ctx.newClient(privateId)
if err != nil {
return nil, verror.Internalf("error creating client: %v", err)
@@ -785,12 +747,12 @@
// Fetch and adapt signature from the SignatureManager
ctx := wsp.ctx.rt.TODOContext()
- sig, err := wsp.signatureManager.signature(ctx, name, client)
+ sig, err := wsp.signatureManager.Signature(ctx, name, client)
if err != nil {
return nil, verror.Internalf("error getting service signature for %s: %v", name, err)
}
- return NewJSONServiceSignature(*sig), nil
+ return signature.NewJSONServiceSignature(*sig), nil
}
// handleSignatureRequest uses signature manager to get and cache signature of a remote server
@@ -799,7 +761,7 @@
var request signatureRequest
decoder := json.NewDecoder(bytes.NewBufferString(data))
if err := decoder.Decode(&request); err != nil {
- w.sendError(verror.Internalf("can't unmarshal JSONMessage: %v", err))
+ w.Error(verror.Internalf("can't unmarshal JSONMessage: %v", err))
return
}
@@ -807,25 +769,20 @@
wsp.ctx.logger.VI(2).Info("private id is", request.PrivateId)
jsSig, err := wsp.getSignature(request.Name, request.PrivateId)
if err != nil {
- w.sendError(err)
+ w.Error(err)
return
}
// Send the signature back
- signatureData := response{Type: responseFinal, Message: jsSig}
- if err := vom.ObjToJSON(w, vom.ValueOf(signatureData)); err != nil {
- w.sendError(verror.Internalf("error marshalling results: %v", err))
- return
- }
- if err := w.FinishMessage(); err != nil {
- w.logger.Error("WSPR: error finishing message: ", err)
+ if err := w.Send(lib.ResponseFinal, jsSig); err != nil {
+ w.Error(verror.Internalf("error marshalling results: %v", err))
return
}
}
func (ctx *WSPR) setup() {
// Cache up to 20 identity.PrivateID->ipc.Client mappings
- ctx.clientCache = NewClientCache(20)
+ ctx.clientCache = lib.NewClientCache(20)
}
// Starts the proxy and listens for requests. This method is blocking.
diff --git a/services/wspr/wsprd/lib/wspr_test.go b/services/wsprd/wspr/wspr_test.go
similarity index 88%
rename from services/wspr/wsprd/lib/wspr_test.go
rename to services/wsprd/wspr/wspr_test.go
index 018270d..fd697ee 100644
--- a/services/wspr/wsprd/lib/wspr_test.go
+++ b/services/wsprd/wspr/wspr_test.go
@@ -1,4 +1,4 @@
-package lib
+package wspr
import (
"bytes"
@@ -8,6 +8,9 @@
"sync"
"testing"
"time"
+ "veyron/services/wsprd/ipc/client"
+ "veyron/services/wsprd/lib"
+ "veyron/services/wsprd/signature"
"veyron2"
"veyron2/ipc"
"veyron2/naming"
@@ -132,7 +135,6 @@
type testWriter struct {
sync.Mutex
stream []response
- buf bytes.Buffer
err error
logger vlog.Logger
// If this channel is set then a message will be sent
@@ -140,29 +142,32 @@
notifier chan bool
}
-func (w *testWriter) Write(p []byte) (int, error) {
- return w.buf.Write(p)
-
-}
-
-func (w *testWriter) sendError(err error) {
- w.err = err
-}
-
-func (w *testWriter) FinishMessage() error {
- var resp response
- p := w.buf.Bytes()
- w.buf.Reset()
- if err := json.Unmarshal(p, &resp); err != nil {
+func (w *testWriter) Send(responseType lib.ResponseType, msg interface{}) error {
+ w.Lock()
+ defer w.Unlock()
+ // We serialize and deserialize the reponse so that we can do deep equal with
+ // messages that contain non-exported structs.
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(response{Type: responseType, Message: msg}); err != nil {
return err
}
- w.Lock()
- w.stream = append(w.stream, resp)
+
+ var r response
+
+ if err := json.NewDecoder(&buf).Decode(&r); err != nil {
+ return err
+ }
+
+ w.stream = append(w.stream, r)
if w.notifier != nil {
w.notifier <- true
}
- w.Unlock()
return nil
+
+}
+
+func (w *testWriter) Error(err error) {
+ w.err = err
}
func (w *testWriter) streamLength() int {
@@ -204,18 +209,18 @@
}
}
-var adderServiceSignature JSONServiceSignature = JSONServiceSignature{
- "add": JSONMethodSignature{
+var adderServiceSignature signature.JSONServiceSignature = signature.JSONServiceSignature{
+ "add": signature.JSONMethodSignature{
InArgs: []string{"A", "B"},
NumOutArgs: 2,
IsStreaming: false,
},
- "divide": JSONMethodSignature{
+ "divide": signature.JSONMethodSignature{
InArgs: []string{"A", "B"},
NumOutArgs: 2,
IsStreaming: false,
},
- "streamingAdd": JSONMethodSignature{
+ "streamingAdd": signature.JSONMethodSignature{
InArgs: []string{},
NumOutArgs: 2,
IsStreaming: true,
@@ -275,7 +280,7 @@
if len(test.streamingInputs) > 0 {
signal = make(chan ipc.Stream, 1)
wsp.outstandingStreams[0] = outstandingStream{
- stream: startQueueingStream(signal),
+ stream: client.StartQueueingStream(signal),
inType: test.streamingInputType,
}
go func() {
@@ -294,6 +299,7 @@
IsStreaming: signal != nil,
}
wsp.sendVeyronRequest(0, &request, &writer, signal)
+
checkResponses(&writer, test.expectedStream, test.expectedError, t)
}
@@ -304,8 +310,8 @@
numOutArgs: 2,
expectedStream: []response{
response{
- Message: []interface{}{float64(5)},
- Type: responseFinal,
+ Message: []interface{}{5.0},
+ Type: lib.ResponseFinal,
},
},
})
@@ -330,27 +336,27 @@
expectedStream: []response{
response{
Message: 1.0,
- Type: responseStream,
+ Type: lib.ResponseStream,
},
response{
Message: 3.0,
- Type: responseStream,
+ Type: lib.ResponseStream,
},
response{
Message: 6.0,
- Type: responseStream,
+ Type: lib.ResponseStream,
},
response{
Message: 10.0,
- Type: responseStream,
+ Type: lib.ResponseStream,
},
response{
Message: nil,
- Type: responseStreamClose,
+ Type: lib.ResponseStreamClose,
},
response{
Message: []interface{}{10.0},
- Type: responseFinal,
+ Type: lib.ResponseFinal,
},
},
})
@@ -385,7 +391,7 @@
writer := testWriter{
logger: wspr.logger,
}
- wsp.writerCreator = func(int64) clientWriter {
+ wsp.writerCreator = func(int64) lib.ClientWriter {
return &writer
}
wsp.setup()
@@ -404,9 +410,8 @@
defer rt.mounttableServer.Stop()
defer rt.proxyServer.Shutdown()
defer rt.wsp.cleanup()
-
if err != nil {
- t.Errorf("could not serve server %v", err)
+ t.Fatalf("could not serve server %v", err)
}
if len(rt.writer.stream) != 1 {
@@ -416,7 +421,7 @@
resp := rt.writer.stream[0]
- if resp.Type != responseFinal {
+ if resp.Type != lib.ResponseFinal {
t.Errorf("unknown stream message Got: %v, expected: serve response", resp)
return
}
@@ -469,14 +474,14 @@
err *verror.Standard
}
-func sendServerStream(t *testing.T, wsp *websocketPipe, test *jsServerTestCase, w clientWriter) {
+func sendServerStream(t *testing.T, wsp *websocketPipe, test *jsServerTestCase, w lib.ClientWriter) {
for _, msg := range test.serverStream {
wsp.sendParsedMessageOnStream(0, msg, w)
}
- serverReply := serverRPCReply{
- Results: []interface{}{test.finalResponse},
- Err: test.err,
+ serverReply := map[string]interface{}{
+ "Results": []interface{}{test.finalResponse},
+ "Err": test.err,
}
bytes, err := json.Marshal(serverReply)
@@ -503,7 +508,7 @@
resp := rt.writer.stream[0]
- if resp.Type != responseFinal {
+ if resp.Type != lib.ResponseFinal {
t.Errorf("unknown stream message Got: %v, expected: serve response", resp)
return
}
@@ -533,14 +538,14 @@
expectedWebsocketMessage := []response{
response{
- Type: responseServerRequest,
+ Type: lib.ResponseServerRequest,
Message: map[string]interface{}{
- "serverId": 0.0,
- "method": lowercaseFirstCharacter(test.method),
- "args": test.inArgs,
- "context": map[string]interface{}{
- "name": "adder",
- "suffix": "adder",
+ "ServerId": 0.0,
+ "Method": lib.LowercaseFirstCharacter(test.method),
+ "Args": test.inArgs,
+ "Context": map[string]interface{}{
+ "Name": "adder",
+ "Suffix": "adder",
},
},
},
@@ -551,7 +556,7 @@
t.Errorf("didn't recieve expected message: %v", err)
}
for _, msg := range test.clientStream {
- expectedWebsocketMessage = append(expectedWebsocketMessage, response{Type: responseStream, Message: msg})
+ expectedWebsocketMessage = append(expectedWebsocketMessage, response{Type: lib.ResponseStream, Message: msg})
if err := call.Send(msg); err != nil {
t.Errorf("unexpected error while sending %v: %v", msg, err)
}
@@ -562,7 +567,7 @@
t.Errorf("didn't recieve expected message: %v", err)
}
- expectedWebsocketMessage = append(expectedWebsocketMessage, response{Type: responseStreamClose})
+ expectedWebsocketMessage = append(expectedWebsocketMessage, response{Type: lib.ResponseStreamClose})
expectedStream := test.serverStream
go sendServerStream(t, rt.wsp, &test, rt.writer)
diff --git a/tools/playground/builder/vbuild.go b/tools/playground/builder/vbuild.go
index 9a3b9b9..0b8f174 100644
--- a/tools/playground/builder/vbuild.go
+++ b/tools/playground/builder/vbuild.go
@@ -6,6 +6,7 @@
import (
"bufio"
"encoding/json"
+ "flag"
"fmt"
"go/parser"
"go/token"
@@ -22,7 +23,7 @@
)
const RUN_TIMEOUT = time.Second
-const debug = false
+var debug = flag.Bool("v", false, "Verbose mode")
type CodeFile struct {
Name string
@@ -55,7 +56,7 @@
}
func Log(args ...interface{}) {
- if debug {
+ if *debug {
log.Println(args...)
}
}
@@ -108,6 +109,7 @@
}
func main() {
+ flag.Parse()
r, err := ParseRequest(os.Stdin)
if err != nil {
log.Fatal(err)
@@ -275,7 +277,7 @@
if groups := pat.FindStringSubmatch(line); groups != nil {
ch <- groups[1]
} else {
- Log(line)
+ Log("mounttabld: %s", line)
}
}
close(ch)
diff --git a/tools/profile/impl/impl.go b/tools/profile/impl/impl.go
index cef78c8..b90e8a6 100644
--- a/tools/profile/impl/impl.go
+++ b/tools/profile/impl/impl.go
@@ -8,6 +8,7 @@
"veyron/services/mgmt/repository"
"veyron2/rt"
+ "veyron2/services/mgmt/build"
)
var cmdLabel = &cmdline.Command{
@@ -105,10 +106,12 @@
// TODO(rthellend): Read an actual specification from a file.
spec := profile.Specification{
- Format: profile.Format{Name: "elf", Attributes: map[string]string{"os": "linux", "arch": "amd64"}},
+ Arch: build.AMD64,
+ Description: "Example profile to test the profile manager implementation.",
+ Format: build.ELF,
Libraries: map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
Label: "example",
- Description: "Example profile to test the profile manager implementation.",
+ OS: build.Linux,
}
if err := p.Put(rt.R().NewContext(), spec); err != nil {
return err
diff --git a/tools/profile/impl/impl_test.go b/tools/profile/impl/impl_test.go
index 8500787..45ccf14 100644
--- a/tools/profile/impl/impl_test.go
+++ b/tools/profile/impl/impl_test.go
@@ -15,16 +15,19 @@
"veyron2/naming"
"veyron2/rt"
"veyron2/security"
+ "veyron2/services/mgmt/build"
"veyron2/vlog"
)
var (
// spec is an example profile specification used throughout the test.
spec = profile.Specification{
- Format: profile.Format{Name: "elf", Attributes: map[string]string{"os": "linux"}},
+ Arch: build.AMD64,
+ Description: "Example profile to test the profile repository implementation.",
+ Format: build.ELF,
Libraries: map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
Label: "example",
- Description: "Example profile to test the profile repository implementation.",
+ OS: build.Linux,
}
)
diff --git a/tools/vrpc/test_base/test_base.vdl.go b/tools/vrpc/test_base/test_base.vdl.go
index 9332b43..3656040 100644
--- a/tools/vrpc/test_base/test_base.vdl.go
+++ b/tools/vrpc/test_base/test_base.vdl.go
@@ -87,14 +87,26 @@
type TypeTesterStreamingOutputStream interface {
// Recv returns the next item in the input stream, blocking until
- // an item is available. Returns io.EOF to indicate graceful end of input.
+ // an item is available. Returns io.EOF to indicate graceful end of
+ // input.
Recv() (item bool, err error)
- // Finish closes the stream and returns the positional return values for
+ // Finish blocks until the server is done and returns the positional
+ // return values for call.
+ //
+ // If Cancel has been called, Finish will return immediately; the output of
+ // Finish could either be an error signalling cancelation, or the correct
+ // positional return values from the server depending on the timing of the
// call.
+ //
+ // Calling Finish is mandatory for releasing stream resources, unless Cancel
+ // has been called or any of the other methods return a non-EOF error.
+ // Finish should be called at most once.
Finish() (err error)
- // Cancel cancels the RPC, notifying the server to stop processing.
+ // Cancel cancels the RPC, notifying the server to stop processing. It
+ // is safe to call Cancel concurrently with any of the other stream methods.
+ // Calling Cancel after Finish has returned is a no-op.
Cancel()
}
@@ -123,7 +135,7 @@
// StreamingOutput in the service interface TypeTester.
type TypeTesterServiceStreamingOutputStream interface {
// Send places the item onto the output stream, blocking if there is no buffer
- // space available.
+ // space available. If the client has canceled, an error is returned.
Send(item bool) error
}