fix for reactive forwarding failing in a
distributed setting.

Change-Id: I992d62bbbd3d873bc8715419592951704903c49d

making the ECHostStore respect sequentiality of events.

Change-Id: I14fa65fc78742c3ea7d417cddefef9f171472246
diff --git a/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
new file mode 100644
index 0000000..836a3c2
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2015 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.
+ */
+package org.onosproject.store.host.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+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.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostStore;
+import org.onosproject.net.host.HostStoreDelegate;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.onosproject.net.DefaultAnnotations.merge;
+import static org.onosproject.net.host.HostEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of hosts using a {@code EventuallyConsistentMap}.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedHostStore
+    extends AbstractStore<HostEvent, HostStoreDelegate>
+    implements HostStore {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private ConsistentMap<HostId, DefaultHost> host;
+    private Map<HostId, DefaultHost> hosts;
+
+    private final ConcurrentHashMap<HostId, DefaultHost> prevHosts =
+            new ConcurrentHashMap<>();
+
+    private MapEventListener<HostId, DefaultHost> hostLocationTracker =
+            new HostLocationTracker();
+
+    @Activate
+    public void activate() {
+        KryoNamespace.Builder hostSerializer = KryoNamespace.newBuilder()
+                .register(KryoNamespaces.API);
+
+        host = storageService.<HostId, DefaultHost>consistentMapBuilder()
+                .withName("onos-hosts")
+                .withRelaxedReadConsistency()
+                .withSerializer(Serializer.using(hostSerializer.build()))
+                .build();
+
+        hosts = host.asJavaMap();
+
+        host.addListener(hostLocationTracker);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        host.removeListener(hostLocationTracker);
+        prevHosts.clear();
+
+        log.info("Stopped");
+    }
+
+    // TODO No longer need to return HostEvent
+    @Override
+    public HostEvent createOrUpdateHost(ProviderId providerId,
+                                        HostId hostId,
+                                        HostDescription hostDescription,
+                                        boolean replaceIPs) {
+        // TODO: We need a way to detect conflicting changes and abort update.
+        //       (BOC) Compute might do this for us.
+
+        hosts.compute(hostId, (id, existingHost) -> {
+            HostLocation location = hostDescription.location();
+
+            final Set<IpAddress> addresses;
+            if (existingHost == null || replaceIPs) {
+                addresses = ImmutableSet.copyOf(hostDescription.ipAddress());
+            } else {
+                addresses = Sets.newHashSet(existingHost.ipAddresses());
+                addresses.addAll(hostDescription.ipAddress());
+            }
+
+            final Annotations annotations;
+            if (existingHost != null) {
+                annotations = merge((DefaultAnnotations) existingHost.annotations(),
+                                    hostDescription.annotations());
+            } else {
+                annotations = hostDescription.annotations();
+            }
+
+            return new DefaultHost(providerId,
+                                   hostId,
+                                   hostDescription.hwAddress(),
+                                   hostDescription.vlan(),
+                                   location,
+                                   addresses,
+                                   annotations);
+        });
+
+        return null;
+    }
+
+    // TODO No longer need to return HostEvent
+    @Override
+    public HostEvent removeHost(HostId hostId) {
+        hosts.remove(hostId);
+        return null;
+    }
+
+    // TODO No longer need to return HostEvent
+    @Override
+    public HostEvent removeIp(HostId hostId, IpAddress ipAddress) {
+        hosts.compute(hostId, (id, existingHost) -> {
+            if (existingHost != null) {
+                checkState(Objects.equals(hostId.mac(), existingHost.mac()),
+                        "Existing and new MAC addresses differ.");
+                checkState(Objects.equals(hostId.vlanId(), existingHost.vlan()),
+                        "Existing and new VLANs differ.");
+
+                Set<IpAddress> addresses = existingHost.ipAddresses();
+                if (addresses != null && addresses.contains(ipAddress)) {
+                    addresses = new HashSet<>(existingHost.ipAddresses());
+                    addresses.remove(ipAddress);
+                    return new DefaultHost(existingHost.providerId(),
+                            hostId,
+                            existingHost.mac(),
+                            existingHost.vlan(),
+                            existingHost.location(),
+                            ImmutableSet.copyOf(addresses),
+                            existingHost.annotations());
+                } else {
+                    return existingHost;
+                }
+            }
+            return null;
+        });
+        return null;
+    }
+
+    @Override
+    public int getHostCount() {
+        return hosts.size();
+    }
+
+    @Override
+    public Iterable<Host> getHosts() {
+        return ImmutableSet.copyOf(hosts.values());
+    }
+
+    @Override
+    public Host getHost(HostId hostId) {
+        return hosts.get(hostId);
+    }
+
+    @Override
+    public Set<Host> getHosts(VlanId vlanId) {
+        return filter(hosts.values(), host -> Objects.equals(host.vlan(), vlanId));
+    }
+
+    @Override
+    public Set<Host> getHosts(MacAddress mac) {
+        return filter(hosts.values(), host -> Objects.equals(host.mac(), mac));
+    }
+
+    @Override
+    public Set<Host> getHosts(IpAddress ip) {
+        return filter(hosts.values(), host -> host.ipAddresses().contains(ip));
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+        Set<Host> filtered = hosts.entrySet().stream()
+                .filter(entry -> entry.getValue().location().equals(connectPoint))
+                .map(Map.Entry::getValue)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(filtered);
+    }
+
+    @Override
+    public Set<Host> getConnectedHosts(DeviceId deviceId) {
+        Set<Host> filtered = hosts.entrySet().stream()
+                .filter(entry -> entry.getValue().location().deviceId().equals(deviceId))
+                .map(Map.Entry::getValue)
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(filtered);
+    }
+
+    private Set<Host> filter(Collection<DefaultHost> collection, Predicate<DefaultHost> predicate) {
+        return collection.stream().filter(predicate).collect(Collectors.toSet());
+    }
+
+    private class HostLocationTracker implements MapEventListener<HostId, DefaultHost> {
+        @Override
+        public void event(MapEvent<HostId, DefaultHost> event) {
+            DefaultHost host = checkNotNull(event.value().value());
+            if (event.type() == MapEvent.Type.INSERT) {
+                Host prevHost = prevHosts.put(host.id(), host);
+                if (prevHost == null) {
+                    notifyDelegate(new HostEvent(HOST_ADDED, host));
+                } else if (!Objects.equals(prevHost.location(), host.location())) {
+                    notifyDelegate(new HostEvent(HOST_MOVED, host, prevHost));
+                } else if (!Objects.equals(prevHost, host)) {
+                    notifyDelegate(new HostEvent(HOST_UPDATED, host, prevHost));
+                }
+            } else if (event.type() == MapEvent.Type.REMOVE) {
+                if (prevHosts.remove(host.id()) != null) {
+                    notifyDelegate(new HostEvent(HOST_REMOVED, host));
+                }
+            }
+        }
+    }
+}