Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java
index 6c0ba30..a22464a 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadRequest.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.service;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Database read request.
  */
@@ -31,6 +33,9 @@
 
     @Override
     public String toString() {
-        return "ReadRequest [tableName=" + tableName + ", key=" + key + "]";
+        return MoreObjects.toStringHelper(getClass())
+                .add("tableName", tableName)
+                .add("key", key)
+                .toString();
     }
 }
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java
index 33b57d2..6d28fc2 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/ReadResult.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.service;
 
+import com.google.common.base.MoreObjects;
+
 
 /**
  * Database read result.
@@ -18,7 +20,7 @@
 
     /**
      * Returns database table name.
-     * @return table name.
+     * @return table name
      */
     public String tableName() {
         return tableName;
@@ -26,7 +28,7 @@
 
     /**
      * Returns database table key.
-     * @return key.
+     * @return key
      */
     public String key() {
         return key;
@@ -39,4 +41,13 @@
     public VersionedValue value() {
         return value;
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("tableName", tableName)
+                .add("key", key)
+                .add("value", value)
+                .toString();
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java
index d88d35e..852fb07 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/VersionedValue.java
@@ -2,6 +2,8 @@
 
 import java.util.Arrays;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Wrapper object that holds the object (as byte array) and its version.
  */
@@ -38,7 +40,9 @@
 
     @Override
     public String toString() {
-        return "VersionedValue [value=" + Arrays.toString(value) + ", version="
-                + version + "]";
+        return MoreObjects.toStringHelper(getClass())
+                .add("version", version)
+                .add("value", Arrays.toString(value))
+                .toString();
     }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java
index 1561c2d..99f73c1 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteRequest.java
@@ -4,6 +4,8 @@
 
 import java.util.Objects;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Database write request.
  */
@@ -67,10 +69,13 @@
 
     @Override
     public String toString() {
-        return "WriteRequest [tableName=" + tableName + ", key=" + key
-                + ", newValue=" + newValue
-                + ", previousVersion=" + previousVersion
-                + ", oldValue=" + oldValue;
+        return MoreObjects.toStringHelper(getClass())
+                .add("tableName", tableName)
+                .add("key", key)
+                .add("newValue", newValue)
+                .add("previousVersion", previousVersion)
+                .add("oldValue", oldValue)
+                .toString();
     }
 
     @Override
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java
index fa20a04..aec3046 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/WriteResult.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.service;
 
+import com.google.common.base.MoreObjects;
+
 
 /**
  * Database write result.
@@ -27,4 +29,13 @@
     public VersionedValue previousValue() {
         return previousValue;
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("tableName", tableName)
+                .add("key", key)
+                .add("previousValue", previousValue)
+                .toString();
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
index d88a214..6de66bc 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/ClusterMessagingProtocol.java
@@ -69,10 +69,10 @@
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    ClusterService clusterService;
+    protected ClusterService clusterService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    ClusterCommunicationService clusterCommunicator;
+    protected ClusterCommunicationService clusterCommunicator;
 
     public static final MessageSubject COPYCAT_PING =
             new MessageSubject("copycat-raft-consensus-ping");
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
index c42f74e..7db4bc7 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/service/impl/DatabaseManager.java
@@ -49,10 +49,10 @@
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    ClusterService clusterService;
+    protected ClusterService clusterService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    ClusterMessagingProtocol copycatMessagingProtocol;
+    protected ClusterMessagingProtocol copycatMessagingProtocol;
 
     public static final String LOG_FILE_PREFIX = "onos-copy-cat-log";
 
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
index e0eea96..db52454 100644
--- a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
@@ -82,7 +82,7 @@
     private final PacketService pktService;
     private final MastershipService mastershipService;
     private Timeout timeout;
-    private boolean isStopped;
+    private volatile boolean isStopped;
 
     /**
      * Instantiates discovery manager for the given physical switch. Creates a
@@ -243,8 +243,10 @@
     public void run(final Timeout t) {
         boolean isMaster = mastershipService.getLocalRole(device.id()) == MASTER;
         if (!isMaster) {
-            // reschedule timer
-            timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
+            if (!isStopped()) {
+                // reschedule timer
+                timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
+            }
             return;
         }
 
@@ -280,16 +282,18 @@
             }
         }
 
-        // reschedule timer
-        timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
+        if (!isStopped()) {
+            // reschedule timer
+            timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
+        }
     }
 
-    public void stop() {
+    public synchronized void stop() {
         timeout.cancel();
         isStopped = true;
     }
 
-    public void start() {
+    public synchronized void start() {
         if (isStopped) {
             timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
             isStopped = false;
diff --git a/web/gui/src/main/webapp/index2.html b/web/gui/src/main/webapp/index2.html
index 703a240..1ddc318 100644
--- a/web/gui/src/main/webapp/index2.html
+++ b/web/gui/src/main/webapp/index2.html
@@ -71,6 +71,7 @@
         var ONOS = $.onos({
             comment: "configuration options",
             startVid: 'topo',
+//            startVid: 'sampleKeys',
             trace: false
         });
     </script>
@@ -82,6 +83,7 @@
     <script src="sample2.js"></script>
     <script src="sampleAlt2.js"></script>
     <script src="sampleRadio.js"></script>
+    <script src="sampleKeys.js"></script>
 
     <!-- Contributed (application) views injected here -->
     <!-- TODO: replace with template marker and inject refs server-side -->
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index b249c09..01e4acd 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -28,7 +28,7 @@
 
     // configuration data
     var config = {
-        useLiveData: true,
+        useLiveData: false,
         debugOn: false,
         debug: {
             showNodeXY: false,
diff --git a/web/gui/src/main/webapp/onos2.js b/web/gui/src/main/webapp/onos2.js
index 6353a6e..f509757 100644
--- a/web/gui/src/main/webapp/onos2.js
+++ b/web/gui/src/main/webapp/onos2.js
@@ -49,13 +49,40 @@
                 ctx: ''
             },
             built = false,
-            errorCount = 0;
+            errorCount = 0,
+            keyHandler = {};
 
         // DOM elements etc.
         var $view,
             $mastRadio;
 
 
+        function whatKey(code) {
+            switch (code) {
+                case 13: return 'enter';
+                case 16: return 'shift';
+                case 17: return 'ctrl';
+                case 18: return 'alt';
+                case 27: return 'esc';
+                case 32: return 'space';
+                case 37: return 'leftArrow';
+                case 38: return 'upArrow';
+                case 39: return 'rightArrow';
+                case 40: return 'downArrow';
+                case 91: return 'cmdLeft';
+                case 93: return 'cmdRight';
+                default:
+                    if ((code >= 48 && code <= 57) ||
+                        (code >= 65 && code <= 90)) {
+                        return String.fromCharCode(code);
+                    } else if (code >= 112 && code <= 123) {
+                        return 'F' + (code - 111);
+                    }
+                    return '.';
+            }
+        }
+
+
         // ..........................................................
         // Internal functions
 
@@ -206,9 +233,11 @@
             // the incoming view, then unload it...
             if (current.view && (current.view.vid !== view.vid)) {
                 current.view.unload();
-                // detach radio buttons, if they were there..
-                $('#mastRadio').children().detach();
 
+                // detach radio buttons, key handlers, etc.
+                $('#mastRadio').children().detach();
+                keyHandler.fn = null;
+                keyHandler.map = {};
             }
 
             // cache new view and context
@@ -283,6 +312,27 @@
             $mastRadio.node().appendChild(btnG.node());
         }
 
+        function setKeyBindings(keyArg) {
+            if ($.isFunction(keyArg)) {
+                // set general key handler callback
+                keyHandler.fn = keyArg;
+            } else {
+                // set specific key filter map
+                keyHandler.map = keyArg;
+            }
+        }
+
+        function keyIn() {
+            var event = d3.event,
+                keyCode = event.keyCode,
+                key = whatKey(keyCode),
+                cb = isF(keyHandler.map[key]) || isF(keyHandler.fn);
+
+            if (cb) {
+                cb(current.view.token(), key, keyCode, event);
+            }
+        }
+
         function resize(e) {
             d3.selectAll('.onosView').call(setViewDimensions);
             // allow current view to react to resize event...
@@ -320,7 +370,6 @@
                 this.radioButtons = null;       // no radio buttons yet
                 this.ok = true;                 // valid view
             }
-
         }
 
         function validateViewArgs(vid) {
@@ -348,7 +397,8 @@
                     width: this.width,
                     height: this.height,
                     uid: this.uid,
-                    setRadio: this.setRadio
+                    setRadio: this.setRadio,
+                    setKeys: this.setKeys
                 }
             },
 
@@ -433,6 +483,10 @@
                 setRadioButtons(this.vid, btnSet, cb);
             },
 
+            setKeys: function (keyArg) {
+                setKeyBindings(keyArg);
+            },
+
             uid: function (id) {
                 return uid(this, id);
             }
@@ -536,6 +590,8 @@
             $(window).on('hashchange', hash);
             $(window).on('resize', resize);
 
+            d3.select('body').on('keydown', keyIn);
+
             // Invoke hashchange callback to navigate to content
             // indicated by the window location hash.
             hash();
@@ -544,7 +600,6 @@
             reportBuildErrors();
         }
 
-
         // export the api and build-UI function
         return {
             ui: uiApi,
diff --git a/web/gui/src/main/webapp/sampleKeys.js b/web/gui/src/main/webapp/sampleKeys.js
new file mode 100644
index 0000000..350f46e
--- /dev/null
+++ b/web/gui/src/main/webapp/sampleKeys.js
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ Sample view to illustrate key bindings.
+
+ @author Simon Hunt
+ */
+
+(function (onos) {
+    'use strict';
+
+    var keyDispatch =  {
+        Z: keyUndo,
+        X: keyCut,
+        C: keyCopy,
+        V: keyPaste,
+        space: keySpace
+    };
+
+    function keyUndo(view) {
+        note(view, 'Z = UNDO');
+    }
+
+    function keyCut(view) {
+        note(view, 'X = CUT');
+    }
+
+    function keyCopy(view) {
+        note(view, 'C = COPY');
+    }
+
+    function keyPaste(view) {
+        note(view, 'V = PASTE');
+    }
+
+    function keySpace(view) {
+        note(view, 'The SpaceBar');
+    }
+
+    function note(view, msg) {
+        view.$div.append('p')
+            .text(msg)
+            .style({
+                'font-size': '10pt',
+                color: 'darkorange',
+                padding: '0 20px',
+                margin: 0
+            });
+    }
+
+    function keyCallback(view, key, keyCode, event) {
+        note(view, 'Key = ' + key + ' KeyCode = ' + keyCode);
+    }
+
+    // Keys using a keyset to target specific keys only
+    function load(view, ctx) {
+        // this maps specific keys to specific functions (1)
+        view.setKeys(keyDispatch);
+        // whereas, this installs a general key handler function (2)
+        view.setKeys(keyCallback);
+
+        // Note that (1) takes precedence over (2)
+
+        view.$div.append('p')
+            .text('Press a key or two (try Z,X,C,V and others) ...')
+            .style('padding', '2px 8px');
+    }
+
+    // == register the view here, with links to lifecycle callbacks
+
+    onos.ui.addView('sampleKeys', {
+        reset: true,    // empty the div on reset
+        load: load
+    });
+
+}(ONOS));