Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/tvue/src/main/webapp/WEB-INF/web.xml b/apps/tvue/src/main/webapp/WEB-INF/web.xml
index 47b3150..12b18bf 100644
--- a/apps/tvue/src/main/webapp/WEB-INF/web.xml
+++ b/apps/tvue/src/main/webapp/WEB-INF/web.xml
@@ -16,7 +16,7 @@
             <param-name>com.sun.jersey.config.property.packages</param-name>
             <param-value>org.onlab.onos.tvue</param-value>
         </init-param>
-        <load-on-startup>1</load-on-startup>
+        <load-on-startup>10</load-on-startup>
     </servlet>
 
     <servlet-mapping>
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/MastershipEvent.java b/core/api/src/main/java/org/onlab/onos/cluster/MastershipEvent.java
index 2a5e62e..15811fb 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/MastershipEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/MastershipEvent.java
@@ -8,6 +8,8 @@
  */
 public class MastershipEvent extends AbstractEvent<MastershipEvent.Type, DeviceId> {
 
+    //do we worry about explicitly setting slaves/equals? probably not,
+    //to keep it simple
     NodeId master;
 
     /**
@@ -28,7 +30,7 @@
      * @param device event device subject
      * @param master master ID subject
      */
-    protected MastershipEvent(Type type, DeviceId device, NodeId master) {
+    public MastershipEvent(Type type, DeviceId device, NodeId master) {
         super(type, device);
         this.master = master;
     }
@@ -42,7 +44,7 @@
      * @param master master ID subject
      * @param time   occurrence time
      */
-    protected MastershipEvent(Type type, DeviceId device, NodeId master, long time) {
+    public MastershipEvent(Type type, DeviceId device, NodeId master, long time) {
         super(type, device, time);
         this.master = master;
     }
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
index 492f0d4..8c3fd50 100644
--- a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
@@ -5,9 +5,12 @@
 import java.util.Set;
 
 import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.MastershipAdminService;
 import org.onlab.onos.cluster.MastershipEvent;
 import org.onlab.onos.cluster.MastershipListener;
@@ -26,6 +29,8 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+@Component(immediate = true)
+@Service
 public class MastershipManager
         extends AbstractProviderRegistry<MastershipProvider, MastershipProviderService>
         implements MastershipService, MastershipAdminService {
@@ -46,7 +51,7 @@
     protected EventDeliveryService eventDispatcher;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected ClusterManager clusterManager;
+    protected ClusterService clusterService;
 
     @Activate
     public void activate() {
@@ -65,7 +70,10 @@
         checkNotNull(nodeId, NODE_ID_NULL);
         checkNotNull(deviceId, DEVICE_ID_NULL);
         checkNotNull(role, ROLE_NULL);
-        store.setRole(nodeId, deviceId, role);
+        MastershipEvent event = store.setRole(nodeId, deviceId, role);
+        if (event != null) {
+            post(event);
+        }
     }
 
     @Override
@@ -83,7 +91,7 @@
     @Override
     public MastershipRole requestRoleFor(DeviceId deviceId) {
         checkNotNull(deviceId, DEVICE_ID_NULL);
-        NodeId id = clusterManager.getLocalNode().id();
+        NodeId id = clusterService.getLocalNode().id();
         return store.getRole(id, deviceId);
     }
 
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 8dcba64..179e214 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
@@ -6,6 +6,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.MastershipService;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
 import org.onlab.onos.net.Device;
@@ -29,6 +30,7 @@
 
 import java.util.List;
 
+import static org.onlab.onos.net.device.DeviceEvent.Type.*;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -58,6 +60,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EventDeliveryService eventDispatcher;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
     @Activate
     public void activate() {
         eventDispatcher.addSink(DeviceEvent.class, listenerRegistry);
@@ -171,6 +176,10 @@
             // If there was a change of any kind, trigger role selection process.
             if (event != null) {
                 log.info("Device {} connected", deviceId);
+                if (event.type().equals(DEVICE_ADDED)) {
+                    MastershipRole role = mastershipService.requestRoleFor(deviceId);
+                    store.setRole(deviceId, role);
+                }
                 Device device = event.subject();
                 provider().roleChanged(device, store.getRole(device.id()));
                 post(event);
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index ecdb177..f27cd78 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -1,30 +1,67 @@
 package org.onlab.onos.net.host.impl;
 
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import org.jboss.netty.util.Timeout;
 import org.jboss.netty.util.TimerTask;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.Port;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions;
 import org.onlab.onos.net.host.HostProvider;
 import org.onlab.onos.net.host.HostService;
-import org.onlab.onos.net.packet.PacketProvider;
+import org.onlab.onos.net.host.HostStore;
+import org.onlab.onos.net.host.PortAddresses;
+import org.onlab.onos.net.packet.DefaultOutboundPacket;
+import org.onlab.onos.net.packet.OutboundPacket;
+import org.onlab.onos.net.packet.PacketService;
 import org.onlab.onos.net.topology.TopologyService;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.util.Timer;
 
+/**
+ * Monitors hosts on the dataplane to detect changes in host data.
+ * <p/>
+ * The HostMonitor can monitor hosts that have already been detected for
+ * changes. At an application's request, it can also monitor and actively
+ * probe for hosts that have not yet been detected (specified by IP address).
+ */
 public class HostMonitor implements TimerTask {
 
+    private static final byte[] DEFAULT_MAC_ADDRESS =
+            MacAddress.valueOf("00:00:00:00:00:01").getAddress();
+
+    private static final byte[] ZERO_MAC_ADDRESS =
+            MacAddress.valueOf("00:00:00:00:00:00").getAddress();
+
+    // TODO put on Ethernet
+    private static final byte[] BROADCAST_MAC =
+            MacAddress.valueOf("ff:ff:ff:ff:ff:ff").getAddress();
+
     private final HostService hostService;
     private final TopologyService topologyService;
     private final DeviceService deviceService;
     private final HostProvider hostProvider;
-    private final PacketProvider packetProvider;
+    private final PacketService packetService;
+    private final HostStore hostStore;
 
-    private final Set<IpPrefix> monitoredAddresses;
+    private final Set<IpAddress> monitoredAddresses;
 
     private final long probeRate;
 
@@ -32,12 +69,14 @@
 
     public HostMonitor(HostService hostService, TopologyService topologyService,
                        DeviceService deviceService,
-                       HostProvider hostProvider, PacketProvider packetProvider) {
+                       HostProvider hostProvider, PacketService packetService,
+                       HostStore hostStore) {
         this.hostService = hostService;
         this.topologyService = topologyService;
         this.deviceService = deviceService;
         this.hostProvider = hostProvider;
-        this.packetProvider = packetProvider;
+        this.packetService = packetService;
+        this.hostStore = hostStore;
 
         monitoredAddresses = new HashSet<>();
 
@@ -46,11 +85,11 @@
         timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
     }
 
-    public void addMonitoringFor(IpPrefix ip) {
+    public void addMonitoringFor(IpAddress ip) {
         monitoredAddresses.add(ip);
     }
 
-    public void stopMonitoring(IpPrefix ip) {
+    public void stopMonitoring(IpAddress ip) {
         monitoredAddresses.remove(ip);
     }
 
@@ -60,8 +99,8 @@
 
     @Override
     public void run(Timeout timeout) throws Exception {
-        for (IpPrefix ip : monitoredAddresses) {
-            Set<Host> hosts = hostService.getHostsByIp(ip);
+        for (IpAddress ip : monitoredAddresses) {
+            Set<Host> hosts = Collections.emptySet(); //TODO hostService.getHostsByIp(ip);
 
             if (hosts.isEmpty()) {
                 sendArpRequest(ip);
@@ -80,28 +119,70 @@
      *
      * @param targetIp IP address to ARP for
      */
-    private void sendArpRequest(IpPrefix targetIp) {
-        // emit ARP packet out appropriate ports
+    private void sendArpRequest(IpAddress targetIp) {
 
-        // if ip in one of the configured (external) subnets
-        //   sent out that port
-        // else (ip isn't in any configured subnet)
-        //   send out all non-external edge ports
-
-        /*for (Device device : deviceService.getDevices()) {
+        // Find ports with an IP address in the target's subnet and sent ARP
+        // probes out those ports.
+        for (Device device : deviceService.getDevices()) {
             for (Port port : deviceService.getPorts(device.id())) {
-                for (IpPrefix ip : port.ipAddresses()) {
-                    if (ip.contains(targetIp)) {
-                        sendProbe(port, targetIp);
-                        continue;
-                    }
+                ConnectPoint cp = new ConnectPoint(device.id(), port.number());
+                PortAddresses addresses = hostStore.getAddressBindingsForPort(cp);
+
+                if (addresses.ip().contains(targetIp)) {
+                    sendProbe(device.id(), port, addresses, targetIp);
                 }
             }
-        }*/
+        }
 
+        // TODO case where no address was found.
+        // Broadcast out internal edge ports?
     }
 
-    private void sendProbe(Port port, IpPrefix targetIp) {
+    private void sendProbe(DeviceId deviceId, Port port, PortAddresses portAddresses,
+            IpAddress targetIp) {
+        Ethernet arpPacket = createArpFor(targetIp, portAddresses);
 
+        List<Instruction> instructions = new ArrayList<>();
+        instructions.add(Instructions.createOutput(port.number()));
+
+        TrafficTreatment treatment =
+                new DefaultTrafficTreatment.Builder()
+                .add(Instructions.createOutput(port.number()))
+                .build();
+
+        OutboundPacket outboundPacket =
+                new DefaultOutboundPacket(deviceId, treatment,
+                        ByteBuffer.wrap(arpPacket.serialize()));
+
+        packetService.emit(outboundPacket);
+    }
+
+    private Ethernet createArpFor(IpAddress targetIp, PortAddresses portAddresses) {
+
+        ARP arp = new ARP();
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
+           .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+           .setProtocolType(ARP.PROTO_TYPE_IP)
+           .setProtocolAddressLength((byte) IpPrefix.INET_LEN);
+
+        byte[] sourceMacAddress;
+        if (portAddresses.mac() == null) {
+            sourceMacAddress = DEFAULT_MAC_ADDRESS;
+        } else {
+            sourceMacAddress = portAddresses.mac().getAddress();
+        }
+
+        arp.setSenderHardwareAddress(sourceMacAddress)
+           .setSenderProtocolAddress(portAddresses.ip().toOctets())
+           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
+           .setTargetProtocolAddress(targetIp.toOctets());
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_ARP)
+                .setDestinationMACAddress(BROADCAST_MAC)
+                .setSourceMACAddress(sourceMacAddress)
+                .setPayload(arp);
+
+        return ethernet;
     }
 }
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
index 209bdd0..2352a96 100644
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
@@ -3,6 +3,9 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.onos.cluster.MastershipListener;
+import org.onlab.onos.cluster.MastershipService;
+import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
@@ -25,9 +28,12 @@
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.net.trivial.impl.SimpleDeviceStore;
 
+import com.google.common.collect.Sets;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.*;
 import static org.onlab.onos.net.Device.Type.SWITCH;
@@ -69,6 +75,7 @@
         registry = mgr;
         mgr.store = new SimpleDeviceStore();
         mgr.eventDispatcher = new TestEventDispatcher();
+        mgr.mastershipService = new TestMastershipService();
         mgr.activate();
 
         service.addListener(listener);
@@ -252,4 +259,31 @@
         }
     }
 
+    private static class TestMastershipService implements MastershipService {
+
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+            return Sets.newHashSet(DID1, DID2);
+        }
+
+        @Override
+        public MastershipRole requestRoleFor(DeviceId deviceId) {
+            return MastershipRole.MASTER;
+        }
+
+        @Override
+        public void addListener(MastershipListener listener) {
+        }
+
+        @Override
+        public void removeListener(MastershipListener listener) {
+        }
+
+    }
+
 }
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java
index 85246e9..332634f 100644
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java
@@ -1,12 +1,17 @@
 package org.onlab.onos.net.device.impl;
 
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import com.hazelcast.config.Config;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.onos.cluster.MastershipListener;
+import org.onlab.onos.cluster.MastershipService;
+import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.net.Device;
@@ -34,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import static org.junit.Assert.*;
@@ -98,6 +104,7 @@
         dstore.activate();
         mgr.store = dstore;
         mgr.eventDispatcher = new TestEventDispatcher();
+        mgr.mastershipService = new TestMastershipService();
         mgr.activate();
 
         service.addListener(listener);
@@ -302,4 +309,32 @@
             setupKryoPool();
         }
     }
+
+    private static class TestMastershipService implements MastershipService {
+
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+            return Sets.newHashSet(DID1, DID2);
+        }
+
+        @Override
+        public MastershipRole requestRoleFor(DeviceId deviceId) {
+            return MastershipRole.MASTER;
+        }
+
+        @Override
+        public void addListener(MastershipListener listener) {
+        }
+
+        @Override
+        public void removeListener(MastershipListener listener) {
+        }
+
+    }
+
 }
diff --git a/core/store/src/main/java/org/onlab/onos/store/impl/StoreManager.java b/core/store/src/main/java/org/onlab/onos/store/impl/StoreManager.java
index 2725175..cfb80a0 100644
--- a/core/store/src/main/java/org/onlab/onos/store/impl/StoreManager.java
+++ b/core/store/src/main/java/org/onlab/onos/store/impl/StoreManager.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.impl;
 
+import com.hazelcast.config.Config;
+import com.hazelcast.config.FileSystemXmlConfig;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
 import de.javakaffee.kryoserializers.URISerializer;
@@ -25,6 +27,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.FileNotFoundException;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -36,6 +39,8 @@
 @Service
 public class StoreManager implements StoreService {
 
+    private static final String HAZELCAST_XML_FILE = "etc/hazelcast.xml";
+
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     protected HazelcastInstance instance;
@@ -44,9 +49,14 @@
 
     @Activate
     public void activate() {
-        instance = Hazelcast.newHazelcastInstance();
-        setupKryoPool();
-        log.info("Started");
+        try {
+            Config config = new FileSystemXmlConfig(HAZELCAST_XML_FILE);
+            instance = Hazelcast.newHazelcastInstance(config);
+            setupKryoPool();
+            log.info("Started");
+        } catch (FileNotFoundException e) {
+            log.error("Unable to configure Hazelcast", e);
+        }
     }
 
     /**
diff --git a/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
index 8e42ffb..e416756 100644
--- a/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
+++ b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
@@ -98,7 +98,7 @@
             availableDevices.add(deviceId);
 
             // For now claim the device as a master automatically.
-            roles.put(deviceId, MastershipRole.MASTER);
+            // roles.put(deviceId, MastershipRole.MASTER);
         }
         return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device, null);
     }
diff --git a/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java
new file mode 100644
index 0000000..371a257
--- /dev/null
+++ b/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java
@@ -0,0 +1,93 @@
+package org.onlab.onos.net.trivial.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.DefaultControllerNode;
+import org.onlab.onos.cluster.MastershipEvent;
+import org.onlab.onos.cluster.MastershipStore;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+
+import static org.onlab.onos.cluster.MastershipEvent.Type.*;
+
+/**
+ * Manages inventory of controller mastership over devices using
+ * trivial in-memory structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleMastershipStore implements MastershipStore {
+
+    public static final IpPrefix LOCALHOST = IpPrefix.valueOf("127.0.0.1");
+
+    private final Logger log = getLogger(getClass());
+
+    private ControllerNode instance;
+
+    protected final ConcurrentMap<DeviceId, MastershipRole> roleMap =
+            new ConcurrentHashMap<DeviceId, MastershipRole>();
+
+    @Activate
+    public void activate() {
+        instance = new DefaultControllerNode(new NodeId("local"), LOCALHOST);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public MastershipEvent setRole(NodeId nodeId, DeviceId deviceId,
+            MastershipRole role) {
+        if (roleMap.get(deviceId) == null) {
+            return null;
+        }
+        roleMap.put(deviceId, role);
+        return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
+    }
+
+    @Override
+    public MastershipEvent addOrUpdateDevice(NodeId instance,
+            DeviceId deviceId, MastershipRole role) {
+        //TODO refine when we do listeners
+        roleMap.put(deviceId, role);
+        return null;
+    }
+
+    @Override
+    public NodeId getMaster(DeviceId deviceId) {
+        return instance.id();
+    }
+
+    @Override
+    public Set<DeviceId> getDevices(NodeId nodeId) {
+        return Collections.unmodifiableSet(roleMap.keySet());
+    }
+
+    @Override
+    public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
+        MastershipRole role = roleMap.get(deviceId);
+        if (role == null) {
+            //say MASTER. If clustered, we'd figure out if anyone's got dibs here.
+            role = MastershipRole.MASTER;
+            roleMap.put(deviceId, role);
+        }
+        return role;
+    }
+
+}
diff --git a/features/features.xml b/features/features.xml
index c9c72a90..7333d89 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -93,8 +93,8 @@
 
     <feature name="onos-app-tvue" version="1.0.0"
              description="ONOS sample topology viewer application">
-        <feature>onos-thirdparty-web</feature>
         <feature>onos-api</feature>
+        <feature>onos-thirdparty-web</feature>
         <bundle>mvn:org.onlab.onos/onos-app-tvue/1.0.0-SNAPSHOT</bundle>
     </feature>
     
diff --git a/tools/build/onos-build b/tools/build/onos-build
new file mode 100755
index 0000000..decf892
--- /dev/null
+++ b/tools/build/onos-build
@@ -0,0 +1,10 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Builds the ONOS from source.
+#-------------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+cd $ONOS_ROOT
+mvn clean install && mvn javadoc:aggregate
\ No newline at end of file
diff --git a/tools/build/onos-package b/tools/build/onos-package
index 03ebce6..d11e183 100755
--- a/tools/build/onos-package
+++ b/tools/build/onos-package
@@ -51,7 +51,7 @@
     $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg 
 
 # Patch the Apache Karaf distribution file to load ONOS features
-perl -pi.old -e 's|^(featuresBoot=.*)|\1,onos-api,onos-core,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-tvue,onos-app-fwd|' \
+perl -pi.old -e 's|^(featuresBoot=.*)|\1,onos-api,onos-core-trivial,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-tvue,onos-app-fwd|' \
     $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg
 
 # Patch the Apache Karaf distribution with ONOS branding bundle
diff --git a/tools/test/bin/onos-test b/tools/build/onos-test
similarity index 86%
rename from tools/test/bin/onos-test
rename to tools/build/onos-test
index d549721..c5e8356 100755
--- a/tools/test/bin/onos-test
+++ b/tools/build/onos-test
@@ -9,5 +9,5 @@
 nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2)
 
 onos-package
-for node in $nodes; do onos-install -f $node; done
+for node in $nodes; do printf "%s: " $node; onos-install -f $node; done
 for node in $nodes; do onos-wait-for-start $node; done
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 328f5f4..821ee43 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -28,8 +28,9 @@
 # Short-hand for 'mvn clean install' for us lazy folk
 alias mci='mvn clean install'
 
-# Short-hand for ONOS build from the top of the source tree.
-alias ob='o && mvn clean install javadoc:aggregate'
+# Short-hand for ONOS build, package and test.
+alias ob='onos-build'
+alias op='onos-package'
 alias ot='onos-test'
 
 # Short-hand for tailing the ONOS (karaf) log 
@@ -41,7 +42,7 @@
 
 # Short-hand to launch API docs and sample topology viewer GUI
 alias docs='open $ONOS_ROOT/target/site/apidocs/index.html'
-alias gui='open http://localhost:8181/onos/tvue'
+alias gui='onos-gui'
 
 
 # Test related conveniences
diff --git a/tools/package/bin/onos-ctl b/tools/package/bin/onos-service
similarity index 100%
rename from tools/package/bin/onos-ctl
rename to tools/package/bin/onos-service
diff --git a/tools/package/debian/onos.conf b/tools/package/debian/onos.conf
index d398bda..0d4cd6e 100644
--- a/tools/package/debian/onos.conf
+++ b/tools/package/debian/onos.conf
@@ -16,6 +16,6 @@
 script
   [ -f /opt/onos/options ] && . /opt/onos/options
   start-stop-daemon --signal INT --start --chuid sdn \
-    --exec /opt/onos/bin/onos-ctl -- $ONOS_OPTS \
+    --exec /opt/onos/bin/onos-service -- $ONOS_OPTS \
         >/opt/onos/var/stdout.log 2>/opt/onos/var/stderr.log
 end script
diff --git a/tools/package/etc/hazelcast.xml b/tools/package/etc/hazelcast.xml
index abd6223..b950768 100644
--- a/tools/package/etc/hazelcast.xml
+++ b/tools/package/etc/hazelcast.xml
@@ -1,5 +1,27 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.2.xsd"
+<!--
+  ~ Copyright (c) 2008-2013, Hazelcast, Inc. All Rights Reserved.
+  ~
+  ~ 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.
+  -->
+
+<!--
+    The default Hazelcast configuration. This is used when:
+
+    - no hazelcast.xml if present
+
+-->
+<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.3.xsd"
            xmlns="http://www.hazelcast.com/schema/config"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <group>
@@ -11,8 +33,8 @@
         <port auto-increment="true" port-count="100">5701</port>
         <outbound-ports>
             <!--
-                Allowed port range when connecting to other nodes.
-                0 or * means use system provided port.
+            Allowed port range when connecting to other nodes.
+            0 or * means use system provided port.
             -->
             <ports>0</ports>
         </outbound-ports>
@@ -24,18 +46,6 @@
             <tcp-ip enabled="false">
                 <interface>127.0.0.1</interface>
             </tcp-ip>
-            <aws enabled="false">
-                <access-key>my-access-key</access-key>
-                <secret-key>my-secret-key</secret-key>
-                <!--optional, default is us-east-1 -->
-                <region>us-west-1</region>
-                <!--optional, default is ec2.amazonaws.com. If set, region shouldn't be set as it will override this property -->
-                <host-header>ec2.amazonaws.com</host-header>
-                <!-- optional, only instances belonging to this group will be discovered, default will try all running instances -->
-                <security-group-name>hazelcast-sg</security-group-name>
-                <tag-key>type</tag-key>
-                <tag-value>hz-nodes</tag-value>
-            </aws>
         </join>
         <interfaces enabled="true">
             <interface>192.168.56.*</interface>
@@ -61,9 +71,9 @@
         </symmetric-encryption>
     </network>
     <partition-group enabled="false"/>
-    <executor-service>
+    <executor-service name="default">
         <pool-size>16</pool-size>
-        <!-- Queue capacity. 0 means Integer.MAX_VALUE -->
+        <!--Queue capacity. 0 means Integer.MAX_VALUE.-->
         <queue-capacity>0</queue-capacity>
     </executor-service>
     <queue name="default">
@@ -81,22 +91,24 @@
             fail-safety. 0 means no backup.
         -->
         <backup-count>1</backup-count>
+
         <!--
             Number of async backups. 0 means no backup.
         -->
         <async-backup-count>0</async-backup-count>
+
         <empty-queue-ttl>-1</empty-queue-ttl>
     </queue>
-
     <map name="default">
         <!--
-            Data type that will be used for storing recordMap.
-            Possible values:
-                BINARY (default): keys and values will be stored as binary data
-                OBJECT : values will be stored in their object forms
-                OFFHEAP : values will be stored in non-heap region of JVM
+           Data type that will be used for storing recordMap.
+           Possible values:
+           BINARY (default): keys and values will be stored as binary data
+           OBJECT : values will be stored in their object forms
+           OFFHEAP : values will be stored in non-heap region of JVM
         -->
         <in-memory-format>BINARY</in-memory-format>
+
         <!--
             Number of backups. If 1 is set as the backup-count for example,
             then all entries of the map will be copied to another JVM for
@@ -144,6 +156,12 @@
         -->
         <eviction-percentage>25</eviction-percentage>
         <!--
+            Minimum time in milliseconds which should pass before checking
+            if a partition of this map is evictable or not.
+            Default value is 100 millis.
+        -->
+        <min-eviction-check-millis>100</min-eviction-check-millis>
+        <!--
             While recovering from split-brain (network partitioning),
             map entries in the small cluster will merge into the bigger cluster
             based on the policy set here. When an entry merge into the
@@ -159,6 +177,7 @@
             com.hazelcast.map.merge.LatestUpdateMapMergePolicy ; entry with the latest update wins.
         -->
         <merge-policy>com.hazelcast.map.merge.PassThroughMergePolicy</merge-policy>
+
     </map>
 
     <multimap name="default">
@@ -199,5 +218,6 @@
         <portable-version>0</portable-version>
     </serialization>
 
-    <services enable-defaults="true" />
+    <services enable-defaults="true"/>
+
 </hazelcast>
diff --git a/tools/test/bin/onos b/tools/test/bin/onos
index f8e62dc..74bf1cf 100755
--- a/tools/test/bin/onos
+++ b/tools/test/bin/onos
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# ONOS remote command-line client
+# ONOS remote command-line client.
 #-------------------------------------------------------------------------------
 
 [ -n "$1" ] && OCI=$1 && shift
diff --git a/tools/test/bin/onos-check-logs b/tools/test/bin/onos-check-logs
new file mode 100755
index 0000000..19091ec
--- /dev/null
+++ b/tools/test/bin/onos-check-logs
@@ -0,0 +1,13 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Checks the logs of the remote ONOS instance and makes sure they are clean.
+#-------------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+remote=$ONOS_USER@${1:-$OCI}
+
+LOG=$ONOS_INSTALL_DIR/log/karaf.log
+
+ssh $remote "egrep 'ERROR|Exception' $LOG"
diff --git a/tools/test/bin/onos-gui b/tools/test/bin/onos-gui
new file mode 100755
index 0000000..f782751
--- /dev/null
+++ b/tools/test/bin/onos-gui
@@ -0,0 +1,9 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Launches ONOS GUI on the specified node.
+#-------------------------------------------------------------------------------
+
+host=${1:-$OCI}
+host=${host:-localhost}
+
+open http://$host:8181/onos/tvue
\ No newline at end of file
diff --git a/tools/test/bin/onos-install b/tools/test/bin/onos-install
index b270704..ecb6da1 100755
--- a/tools/test/bin/onos-install
+++ b/tools/test/bin/onos-install
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Remotely pushes bits to a remote machine and installs ONOS.
+# Remotely pushes bits to a remote node and installs ONOS on it.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
diff --git a/tools/test/bin/onos-log b/tools/test/bin/onos-log
index 1a205f4..3e0c945 100755
--- a/tools/test/bin/onos-log
+++ b/tools/test/bin/onos-log
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Monitors remote ONOS log file.
+# Monitors remote ONOS log file on the specified node.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
diff --git a/tools/test/bin/onos-push-keys b/tools/test/bin/onos-push-keys
index a469643..fd49f86 100755
--- a/tools/test/bin/onos-push-keys
+++ b/tools/test/bin/onos-push-keys
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Pushes the local id_rsa.pub to the remote ONOS host authorized_keys.
+# Pushes the local id_rsa.pub to the authorized_keys on a remote ONOS node.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
diff --git a/tools/test/bin/onos-service b/tools/test/bin/onos-service
new file mode 100755
index 0000000..d148f67
--- /dev/null
+++ b/tools/test/bin/onos-service
@@ -0,0 +1,9 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Remotely administers the ONOS service on the specified node.
+#-------------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+ssh $ONOS_USER@${1:-$OCI} "sudo service onos ${2:-status}"
\ No newline at end of file
diff --git a/tools/test/bin/onos-ssh b/tools/test/bin/onos-ssh
index 406c9cb..f475f25 100755
--- a/tools/test/bin/onos-ssh
+++ b/tools/test/bin/onos-ssh
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Logs in to the remote ONOS instance.
+# Logs in to the remote ONOS node.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
diff --git a/tools/test/bin/onos-uninstall b/tools/test/bin/onos-uninstall
index bfc3b48..78ff629 100755
--- a/tools/test/bin/onos-uninstall
+++ b/tools/test/bin/onos-uninstall
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Remotely stops & uninstalls ONOS.
+# Remotely stops & uninstalls ONOS on the specified node.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
diff --git a/tools/test/bin/onos-wait-for-start b/tools/test/bin/onos-wait-for-start
index 0190d6f..442a07d 100755
--- a/tools/test/bin/onos-wait-for-start
+++ b/tools/test/bin/onos-wait-for-start
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Waits for ONOS to reach run-level 100.
+# Waits for ONOS to reach run-level 100 on the specified remote node.
 #-------------------------------------------------------------------------------
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1