Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
index 9934b8d..ec73ce5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
@@ -13,7 +13,7 @@
 
     /**
      * Triggers an asynchronous probe of the specified device, intended to
-     * determine whether the host is present or not. An indirect result of this
+     * determine whether the device is present or not. An indirect result of this
      * should be invocation of
      * {@link org.onlab.onos.net.device.DeviceProviderService#deviceConnected} )} or
      * {@link org.onlab.onos.net.device.DeviceProviderService#deviceDisconnected}
diff --git a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
index fd47dc7..a157e50 100644
--- a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
@@ -161,6 +161,17 @@
         }
     }
 
+    // Queries a device for port information.
+    private void queryPortInfo(DeviceId deviceId) {
+        Device device = store.getDevice(deviceId);
+        // FIXME: Device might not be there yet. (eventual consistent)
+        if (device == null) {
+            return;
+        }
+        DeviceProvider provider = getProvider(device.providerId());
+        provider.triggerProbe(device);
+    }
+
     @Override
     public void removeDevice(DeviceId deviceId) {
         checkNotNull(deviceId, DEVICE_ID_NULL);
@@ -210,8 +221,6 @@
             log.info("Device {} connected", deviceId);
             // check my Role
             MastershipRole role = mastershipService.requestRoleFor(deviceId);
-            log.info("## - our role for {} is {} [master is {}]", deviceId, role,
-                    mastershipService.getMasterFor(deviceId));
             if (role != MastershipRole.MASTER) {
                 // TODO: Do we need to explicitly tell the Provider that
                 // this instance is no longer the MASTER? probably not
@@ -265,7 +274,6 @@
             // but if I was the last STANDBY connection, etc. and no one else
             // was there to mark the device offline, this instance may need to
             // temporarily request for Master Role and mark offline.
-            log.info("## for {} role is {}", deviceId, mastershipService.getLocalRole(deviceId));
             if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
                 log.debug("Device {} disconnected, but I am not the master", deviceId);
                 //let go of ability to be backup
@@ -373,7 +381,6 @@
             final DeviceId did = event.subject();
             final NodeId myNodeId = clusterService.getLocalNode().id();
 
-            log.info("## got Mastershipevent for dev {}", did);
             if (myNodeId.equals(event.roleInfo().master())) {
                 MastershipTerm term = termService.getMastershipTerm(did);
 
@@ -384,7 +391,6 @@
                     return;
                 }
 
-                log.info("## setting term for CPS as new master for {}", did);
                 // only set the new term if I am the master
                 deviceClockProviderService.setMastershipTerm(did, term);
 
@@ -404,6 +410,7 @@
                                     device.serialNumber(), device.chassisId()));
                 }
                 //TODO re-collect device information to fix potential staleness
+                queryPortInfo(did);
                 applyRole(did, MastershipRole.MASTER);
             } else if (event.roleInfo().backups().contains(myNodeId)) {
                 applyRole(did, MastershipRole.STANDBY);
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index 67e0867..01514d4 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -299,7 +299,8 @@
         private void extraneousFlow(FlowRule flowRule) {
             checkNotNull(flowRule, FLOW_RULE_NULL);
             checkValidity();
-            removeFlowRules(flowRule);
+            FlowRuleProvider frp = getProvider(flowRule.deviceId());
+            frp.removeFlowRule(flowRule);
             log.debug("Flow {} is on switch but not in store.", flowRule);
         }
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
index 4bca215..9b1a2e0 100644
--- a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
@@ -9,6 +9,7 @@
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.Path;
+
 import org.onlab.onos.net.flow.FlowRule;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleListener;
@@ -35,12 +36,14 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StatisticStore statisticStore;
 
+
     private final InternalFlowRuleListener listener = new InternalFlowRuleListener();
 
     @Activate
     public void activate() {
         flowRuleService.addListener(listener);
         log.info("Started");
+
     }
 
     @Deactivate
@@ -81,7 +84,22 @@
 
         @Override
         public void event(FlowRuleEvent event) {
-
+//            FlowRule rule = event.subject();
+//            switch (event.type()) {
+//                case RULE_ADDED:
+//                case RULE_UPDATED:
+//                    if (rule instanceof FlowEntry) {
+//                        statisticStore.addOrUpdateStatistic((FlowEntry) rule);
+//                    }
+//                    break;
+//                case RULE_ADD_REQUESTED:
+//                    statisticStore.prepareForStatistics(rule);
+//                    break;
+//                case RULE_REMOVE_REQUESTED:
+//                case RULE_REMOVED:
+//                    statisticStore.removeFromStatistics(rule);
+//                    break;
+//            }
         }
     }
 
diff --git a/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java b/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
index 9eb3980..d2cbc24 100644
--- a/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
+++ b/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
@@ -15,6 +15,7 @@
         implements EventDeliveryService {
 
     @Override
+    @SuppressWarnings("unchecked")
     public void post(Event event) {
         EventSink sink = getSink(event.getClass());
         checkState(sink != null, "No sink for event %s", event);
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index ca7cc07..2a7384b 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -1,15 +1,5 @@
 package org.onlab.onos.net.flow.impl;
 
-import static java.util.Collections.EMPTY_LIST;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -65,6 +55,16 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
+import static java.util.Collections.EMPTY_LIST;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
+
 /**
  * Test codifying the flow rule service & flow rule provider service contracts.
  */
@@ -176,6 +176,7 @@
 
     // TODO: If preserving iteration order is a requirement, redo FlowRuleStore.
     //backing store is sensitive to the order of additions/removals
+    @SuppressWarnings("unchecked")
     private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
         Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
         Iterable<FlowEntry> rules = service.getFlowEntries(DID);
@@ -539,6 +540,7 @@
             }
 
             @Override
+            @SuppressWarnings("unchecked")
             public CompletedBatchOperation get()
                     throws InterruptedException, ExecutionException {
                 return new CompletedBatchOperation(true, EMPTY_LIST);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
index 5f97afd..c789c96 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
@@ -35,6 +35,8 @@
                                Class<InternalDeviceEvent> type) {
         ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
         DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+        @SuppressWarnings("unchecked")
         Timestamped<DeviceDescription> deviceDescription
             = (Timestamped<DeviceDescription>) kryo.readClassAndObject(input);
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
index 4f4a9e6..2facb7e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
@@ -37,6 +37,8 @@
                                Class<InternalPortEvent> type) {
         ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
         DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+        @SuppressWarnings("unchecked")
         Timestamped<List<PortDescription>> portDescriptions
             = (Timestamped<List<PortDescription>>) kryo.readClassAndObject(input);
 
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index a96cacb..d312af5 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -177,6 +177,7 @@
     public boolean deleteFlowRule(FlowRule rule) {
 
         List<StoredFlowEntry> entries = getFlowEntries(rule.deviceId(), rule.id());
+
         synchronized (entries) {
             for (StoredFlowEntry entry : entries) {
                 if (entry.equals(rule)) {
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
index 0ec8612..12e62e2 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
@@ -82,12 +82,28 @@
     public IntentEvent setState(Intent intent, IntentState state) {
         IntentId id = intent.id();
         states.put(id, state);
-        IntentEvent.Type type = (state == SUBMITTED ? IntentEvent.Type.SUBMITTED :
-                (state == INSTALLED ? IntentEvent.Type.INSTALLED :
-                        (state == FAILED ? IntentEvent.Type.FAILED :
-                                state == WITHDRAWN ? IntentEvent.Type.WITHDRAWN :
-                                        null)));
-        return type == null ? null : new IntentEvent(type, intent);
+        IntentEvent.Type type = null;
+
+        switch (state) {
+        case SUBMITTED:
+            type = IntentEvent.Type.SUBMITTED;
+            break;
+        case INSTALLED:
+            type = IntentEvent.Type.INSTALLED;
+            break;
+        case FAILED:
+            type = IntentEvent.Type.FAILED;
+            break;
+        case WITHDRAWN:
+            type = IntentEvent.Type.WITHDRAWN;
+            break;
+        default:
+            break;
+        }
+        if (type == null) {
+            return null;
+        }
+        return new IntentEvent(type, intent);
     }
 
     @Override
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
index 6fd02bc..1375a20 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
@@ -110,8 +110,7 @@
      *
      * @param role the failed role
      */
-    void returnRoleAssertFailure(RoleState role);
-
+    public void returnRoleAssertFailure(RoleState role);
 
     /**
      * Indicates if this switch is optical.
@@ -120,5 +119,4 @@
      */
     public boolean isOptical();
 
-
 }
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
index 53217da..33e5bdf 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
@@ -20,6 +20,12 @@
     public void switchRemoved(Dpid dpid);
 
     /**
+     * Notify that the switch has changed in some way.
+     * @param dpid the switch that changed
+     */
+    public void switchChanged(Dpid dpid);
+
+    /**
      * Notify that a port has changed.
      * @param dpid the switch on which the change happened.
      * @param status the new state of the port.
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
index 009cd3f..5047867 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
@@ -565,6 +565,9 @@
             @Override
             void processOFStatisticsReply(OFChannelHandler h,
                     OFStatsReply m) {
+                if (m.getStatsType().equals(OFStatsType.PORT_DESC)) {
+                    h.sw.setPortDescReply((OFPortDescStatsReply) m);
+                }
                 h.dispatchMessage(m);
             }
 
@@ -608,6 +611,12 @@
                 h.dispatchMessage(m);
             }
 
+            @Override
+            void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m) {
+                h.sw.setFeaturesReply(m);
+                h.dispatchMessage(m);
+            }
+
         };
 
         private final boolean handshakeComplete;
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
index 565ccb9..cd9b760 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
@@ -27,6 +27,8 @@
 import org.projectfloodlight.openflow.protocol.OFMessage;
 import org.projectfloodlight.openflow.protocol.OFPacketIn;
 import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -146,6 +148,11 @@
                 l.portChanged(dpid, (OFPortStatus) msg);
             }
             break;
+        case FEATURES_REPLY:
+            for (OpenFlowSwitchListener l : ofSwitchListener) {
+                l.switchChanged(dpid);
+            }
+            break;
         case PACKET_IN:
             OpenFlowPacketContext pktCtx = DefaultOpenFlowPacketContext
             .packetContextFromPacketIn(this.getSwitch(dpid),
@@ -154,9 +161,15 @@
                 p.handlePacket(pktCtx);
             }
             break;
+        case STATS_REPLY:
+            OFStatsReply reply = (OFStatsReply) msg;
+            if (reply.getStatsType().equals(OFStatsType.PORT_DESC)) {
+                for (OpenFlowSwitchListener l : ofSwitchListener) {
+                    l.switchChanged(dpid);
+                }
+            }
         case FLOW_REMOVED:
         case ERROR:
-        case STATS_REPLY:
         case BARRIER_REPLY:
             executor.submit(new OFMessageHandler(dpid, msg));
             break;
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 75ce1da..95cd619 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
@@ -344,7 +344,8 @@
     }
 
     private void sendProbes(Long portNumber) {
-       if (mastershipService.getLocalRole(this.device.id()) ==
+       if (device.type() != Device.Type.ROADM &&
+               mastershipService.getLocalRole(this.device.id()) ==
                MastershipRole.MASTER) {
            OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
            pktService.emit(pkt);
diff --git a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
index fcc7810..984e8ae 100644
--- a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -23,7 +23,9 @@
 import org.onlab.onos.openflow.controller.OpenFlowSwitch;
 import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
 import org.onlab.onos.openflow.controller.RoleState;
+import org.onlab.onos.openflow.controller.driver.OpenFlowSwitchDriver;
 import org.onlab.packet.ChassisId;
+import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFPortConfig;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.OFPortState;
@@ -89,6 +91,28 @@
     @Override
     public void triggerProbe(Device device) {
         LOG.info("Triggering probe on device {}", device.id());
+
+        // 1. check device liveness
+        // FIXME if possible, we might want this to be part of
+        // OpenFlowSwitch interface so the driver interface isn't misused.
+        OpenFlowSwitch sw = controller.getSwitch(dpid(device.id().uri()));
+        if (!((OpenFlowSwitchDriver) sw).isConnected()) {
+            providerService.deviceDisconnected(device.id());
+            return;
+        }
+
+        // 2. Prompt an update of port information. Do we have an XID for this?
+        OFFactory fact = sw.factory();
+        switch (fact.getVersion()) {
+            case OF_10:
+                sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build());
+                break;
+            case OF_13:
+                sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build());
+                break;
+            default:
+                LOG.warn("Unhandled protocol version");
+        }
     }
 
     @Override
@@ -141,6 +165,17 @@
             providerService.deviceDisconnected(deviceId(uri(dpid)));
         }
 
+
+        @Override
+        public void switchChanged(Dpid dpid) {
+            if (providerService == null) {
+                return;
+            }
+            DeviceId did = deviceId(uri(dpid));
+            OpenFlowSwitch sw = controller.getSwitch(dpid);
+            providerService.updatePorts(did, buildPortDescriptions(sw.getPorts()));
+        }
+
         @Override
         public void portChanged(Dpid dpid, OFPortStatus status) {
             PortDescription portDescription = buildPortDescription(status.getDesc());
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
index 54265ba..d18a72f 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -233,6 +233,10 @@
         }
 
         @Override
+        public void switchChanged(Dpid dpid) {
+        }
+
+        @Override
         public void portChanged(Dpid dpid, OFPortStatus status) {
             //TODO: Decide whether to evict flows internal store.
         }
@@ -313,6 +317,7 @@
             }
             return false;
         }
+
     }
 
     private class InstallationFuture implements Future<CompletedBatchOperation> {
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
index 7f16eaa..c3f5a68 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
@@ -118,6 +118,12 @@
                     DeviceId.deviceId("of:" + Long.toHexString(dpid.value())));
         }
 
+
+        @Override
+        public void switchChanged(Dpid dpid) {
+            //might not need to do anything since DeviceManager is notified
+        }
+
         @Override
         public void portChanged(Dpid dpid, OFPortStatus status) {
             LinkDiscovery ld = discoverers.get(dpid);
diff --git a/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java b/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
index 2915d4b..7fecbc3 100644
--- a/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
+++ b/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
@@ -6,6 +6,7 @@
 /**
  * Service directory implementation suitable for testing.
  */
+@SuppressWarnings("unchecked")
 public class TestServiceDirectory implements ServiceDirectory {
 
     private ClassToInstanceMap<Object> services = MutableClassToInstanceMap.create();
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 3b0d290..c2cb8f8 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -32,20 +32,20 @@
         <div id="view"></div>
     </div>
 
-    // Initialize the UI...
+    <!-- Initialize the UI...-->
     <script type="text/javascript">
         var ONOS = $.onos({note: "config, if needed"});
     </script>
 
-    // include module files
-    // + mast.js
-    // + nav.js
-    // + .... application views
+    <!-- include module files-->
+    <!-- + mast.js-->
+    <!-- + nav.js-->
+    <!-- + .... application views-->
 
-    // for now, we are just bootstrapping the network visualization
+    <!-- for now, we are just bootstrapping the network visualization-->
     <script src="network.js" type="text/javascript"></script>
 
-    // finally, build the UI
+    <!-- finally, build the UI-->
     <script type="text/javascript">
         $(ONOS.buildUi);
     </script>
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index 81105dc..d7261de 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -100,7 +100,7 @@
 
         network.data.nodes.forEach(function(n) {
             var ypc = yPosConstraintForNode(n),
-                ix = Math.random() * 0.8 * nw + 0.1 * nw,
+                ix = Math.random() * 0.6 * nw + 0.2 * nw,
                 iy = ypc * nh,
                 node = {
                     id: n.id,
@@ -152,11 +152,49 @@
             .attr('width', view.width)
             .attr('height', view.height)
             .append('g')
-            .attr('transform', config.force.translate());
+//            .attr('id', 'zoomable')
+            .attr('transform', config.force.translate())
+//            .call(d3.behavior.zoom().on("zoom", zoomRedraw));
 
-        // TODO: svg.append('defs')
-        // TODO: glow/blur stuff
+//        function zoomRedraw() {
+//            d3.select("#zoomable").attr("transform",
+//                    "translate(" + d3.event.translate + ")"
+//                    + " scale(" + d3.event.scale + ")");
+//        }
+
+        // TODO: svg.append('defs') for markers?
+
+        // TODO: move glow/blur stuff to util script
+        var glow = network.svg.append('filter')
+            .attr('x', '-50%')
+            .attr('y', '-50%')
+            .attr('width', '200%')
+            .attr('height', '200%')
+            .attr('id', 'blue-glow');
+
+        glow.append('feColorMatrix')
+            .attr('type', 'matrix')
+            .attr('values', '0 0 0 0  0 ' +
+                '0 0 0 0  0 ' +
+                '0 0 0 0  .7 ' +
+                '0 0 0 1  0 ');
+
+        glow.append('feGaussianBlur')
+            .attr('stdDeviation', 3)
+            .attr('result', 'coloredBlur');
+
+        glow.append('feMerge').selectAll('feMergeNode')
+            .data(['coloredBlur', 'SourceGraphic'])
+            .enter().append('feMergeNode')
+            .attr('in', String);
+
         // TODO: legend (and auto adjust on scroll)
+//        $('#view').on('scroll', function() {
+//
+//        });
+
+
+
 
         network.link = network.svg.append('g').selectAll('.link')
             .data(network.force.links(), function(d) {return d.id})
@@ -164,27 +202,90 @@
             .attr('class', 'link');
 
         // TODO: drag behavior
-        // TODO: closest node deselect
+        network.draggedThreshold = d3.scale.linear()
+            .domain([0, 0.1])
+            .range([5, 20])
+            .clamp(true);
+
+        function dragged(d) {
+            var threshold = network.draggedThreshold(network.force.alpha()),
+                dx = d.oldX - d.px,
+                dy = d.oldY - d.py;
+            if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+                d.dragged = true;
+            }
+            return d.dragged;
+        }
+
+        network.drag = d3.behavior.drag()
+            .origin(function(d) { return d; })
+            .on('dragstart', function(d) {
+                d.oldX = d.x;
+                d.oldY = d.y;
+                d.dragged = false;
+                d.fixed |= 2;
+            })
+            .on('drag', function(d) {
+                d.px = d3.event.x;
+                d.py = d3.event.y;
+                if (dragged(d)) {
+                    if (!network.force.alpha()) {
+                        network.force.alpha(.025);
+                    }
+                }
+            })
+            .on('dragend', function(d) {
+                if (!dragged(d)) {
+                    selectObject(d, this);
+                }
+                d.fixed &= ~6;
+            });
+
+        $('#view').on('click', function(e) {
+            if (!$(e.target).closest('.node').length) {
+                deselectObject();
+            }
+        });
 
         // TODO: add drag, mouseover, mouseout behaviors
         network.node = network.svg.selectAll('.node')
             .data(network.force.nodes(), function(d) {return d.id})
             .enter().append('g')
-            .attr('class', 'node')
+            .attr('class', function(d) {
+                return 'node ' + d.type;
+            })
             .attr('transform', function(d) {
                 return translate(d.x, d.y);
             })
-        //        .call(network.drag)
-            .on('mouseover', function(d) {})
-            .on('mouseout', function(d) {});
+            .call(network.drag)
+            .on('mouseover', function(d) {
+                if (!selected.obj) {
+                    if (network.mouseoutTimeout) {
+                        clearTimeout(network.mouseoutTimeout);
+                        network.mouseoutTimeout = null;
+                    }
+                    highlightObject(d);
+                }
+            })
+            .on('mouseout', function(d) {
+                if (!selected.obj) {
+                    if (network.mouseoutTimeout) {
+                        clearTimeout(network.mouseoutTimeout);
+                        network.mouseoutTimeout = null;
+                    }
+                    network.mouseoutTimeout = setTimeout(function() {
+                        highlightObject(null);
+                    }, 160);
+                }
+            });
 
         // TODO: augment stroke and fill functions
         network.nodeRect = network.node.append('rect')
             // TODO: css for node rects
             .attr('rx', 5)
             .attr('ry', 5)
-            .attr('stroke', function(d) { return '#000'})
-            .attr('fill', function(d) { return '#ddf'})
+//            .attr('stroke', function(d) { return '#000'})
+//            .attr('fill', function(d) { return '#ddf'})
             .attr('width', 60)
             .attr('height', 24);
 
diff --git a/web/gui/src/main/webapp/onos.css b/web/gui/src/main/webapp/onos.css
index 328e109..548ca57 100644
--- a/web/gui/src/main/webapp/onos.css
+++ b/web/gui/src/main/webapp/onos.css
@@ -13,7 +13,7 @@
  */
 
 span.title {
-    color: red;
+    color: darkblue;
     font-size: 16pt;
     font-style: italic;
 }
@@ -30,7 +30,7 @@
  * === DEBUGGING ======
  */
 svg {
-    border: 1px dashed red;
+    /*border: 1px dashed red;*/
 }
 
 
@@ -64,36 +64,45 @@
     -moz-transition: opacity 250ms;
 }
 
-.node text {
-    fill: #000;
+/*differentiate between packet and optical nodes*/
+svg .node.pkt rect {
+    fill: #77a;
+}
+
+svg .node.opt rect {
+    fill: #7a7;
+}
+
+svg .node text {
+    fill: white;
     font: 10px sans-serif;
     pointer-events: none;
 }
 
-.node.selected rect {
+svg .node.selected rect {
     filter: url(#blue-glow);
 }
 
-.link.inactive,
-.node.inactive rect,
-.node.inactive text {
+svg .link.inactive,
+svg .node.inactive rect,
+svg .node.inactive text {
     opacity: .2;
 }
 
-.node.inactive.selected rect,
-.node.inactive.selected text {
+svg .node.inactive.selected rect,
+svg .node.inactive.selected text {
     opacity: .6;
 }
 
-.legend {
+svg .legend {
     position: fixed;
 }
 
-.legend .category rect {
+svg .legend .category rect {
     stroke-width: 1px;
 }
 
-.legend .category text {
+svg .legend .category text {
     fill: #000;
     font: 10px sans-serif;
     pointer-events: none;
@@ -110,15 +119,15 @@
 #frame {
     width: 100%;
     height: 100%;
-    background-color: #ffd;
+    background-color: #cdf;
 }
 
 #mast {
     height: 32px;
-    background-color: #dda;
+    background-color: #abe;
     vertical-align: baseline;
 }
 
 #main {
-    background-color: #99b;
+    background-color: #99c;
 }