GossipHostStore: add AE support
- modified HostDescription family to hold Set of IpAddresses Change-Id: Id920fdc83817802885e8528af185a5ad590bf999
Showing
12 changed files
with
467 additions
and
21 deletions
| 1 | package org.onlab.onos.net.host; | 1 | package org.onlab.onos.net.host; |
| 2 | 2 | ||
| 3 | +import java.util.Collections; | ||
| 4 | +import java.util.Set; | ||
| 5 | + | ||
| 3 | import org.onlab.onos.net.AbstractDescription; | 6 | import org.onlab.onos.net.AbstractDescription; |
| 4 | import org.onlab.onos.net.HostLocation; | 7 | import org.onlab.onos.net.HostLocation; |
| 5 | import org.onlab.onos.net.SparseAnnotations; | 8 | import org.onlab.onos.net.SparseAnnotations; |
| ... | @@ -7,6 +10,8 @@ import org.onlab.packet.IpPrefix; | ... | @@ -7,6 +10,8 @@ import org.onlab.packet.IpPrefix; |
| 7 | import org.onlab.packet.MacAddress; | 10 | import org.onlab.packet.MacAddress; |
| 8 | import org.onlab.packet.VlanId; | 11 | import org.onlab.packet.VlanId; |
| 9 | 12 | ||
| 13 | +import com.google.common.collect.ImmutableSet; | ||
| 14 | + | ||
| 10 | import static com.google.common.base.MoreObjects.toStringHelper; | 15 | import static com.google.common.base.MoreObjects.toStringHelper; |
| 11 | 16 | ||
| 12 | /** | 17 | /** |
| ... | @@ -18,7 +23,7 @@ public class DefaultHostDescription extends AbstractDescription | ... | @@ -18,7 +23,7 @@ public class DefaultHostDescription extends AbstractDescription |
| 18 | private final MacAddress mac; | 23 | private final MacAddress mac; |
| 19 | private final VlanId vlan; | 24 | private final VlanId vlan; |
| 20 | private final HostLocation location; | 25 | private final HostLocation location; |
| 21 | - private final IpPrefix ip; | 26 | + private final Set<IpPrefix> ip; |
| 22 | 27 | ||
| 23 | /** | 28 | /** |
| 24 | * Creates a host description using the supplied information. | 29 | * Creates a host description using the supplied information. |
| ... | @@ -31,7 +36,7 @@ public class DefaultHostDescription extends AbstractDescription | ... | @@ -31,7 +36,7 @@ public class DefaultHostDescription extends AbstractDescription |
| 31 | public DefaultHostDescription(MacAddress mac, VlanId vlan, | 36 | public DefaultHostDescription(MacAddress mac, VlanId vlan, |
| 32 | HostLocation location, | 37 | HostLocation location, |
| 33 | SparseAnnotations... annotations) { | 38 | SparseAnnotations... annotations) { |
| 34 | - this(mac, vlan, location, null, annotations); | 39 | + this(mac, vlan, location, Collections.<IpPrefix>emptySet(), annotations); |
| 35 | } | 40 | } |
| 36 | 41 | ||
| 37 | /** | 42 | /** |
| ... | @@ -46,11 +51,26 @@ public class DefaultHostDescription extends AbstractDescription | ... | @@ -46,11 +51,26 @@ public class DefaultHostDescription extends AbstractDescription |
| 46 | public DefaultHostDescription(MacAddress mac, VlanId vlan, | 51 | public DefaultHostDescription(MacAddress mac, VlanId vlan, |
| 47 | HostLocation location, IpPrefix ip, | 52 | HostLocation location, IpPrefix ip, |
| 48 | SparseAnnotations... annotations) { | 53 | SparseAnnotations... annotations) { |
| 54 | + this(mac, vlan, location, ImmutableSet.of(ip), annotations); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * Creates a host description using the supplied information. | ||
| 59 | + * | ||
| 60 | + * @param mac host MAC address | ||
| 61 | + * @param vlan host VLAN identifier | ||
| 62 | + * @param location host location | ||
| 63 | + * @param ip host IP addresses | ||
| 64 | + * @param annotations optional key/value annotations map | ||
| 65 | + */ | ||
| 66 | + public DefaultHostDescription(MacAddress mac, VlanId vlan, | ||
| 67 | + HostLocation location, Set<IpPrefix> ip, | ||
| 68 | + SparseAnnotations... annotations) { | ||
| 49 | super(annotations); | 69 | super(annotations); |
| 50 | this.mac = mac; | 70 | this.mac = mac; |
| 51 | this.vlan = vlan; | 71 | this.vlan = vlan; |
| 52 | this.location = location; | 72 | this.location = location; |
| 53 | - this.ip = ip; | 73 | + this.ip = ImmutableSet.copyOf(ip); |
| 54 | } | 74 | } |
| 55 | 75 | ||
| 56 | @Override | 76 | @Override |
| ... | @@ -69,7 +89,7 @@ public class DefaultHostDescription extends AbstractDescription | ... | @@ -69,7 +89,7 @@ public class DefaultHostDescription extends AbstractDescription |
| 69 | } | 89 | } |
| 70 | 90 | ||
| 71 | @Override | 91 | @Override |
| 72 | - public IpPrefix ipAddress() { | 92 | + public Set<IpPrefix> ipAddress() { |
| 73 | return ip; | 93 | return ip; |
| 74 | } | 94 | } |
| 75 | 95 | ... | ... |
| 1 | package org.onlab.onos.net.host; | 1 | package org.onlab.onos.net.host; |
| 2 | 2 | ||
| 3 | +import java.util.Set; | ||
| 4 | + | ||
| 3 | import org.onlab.onos.net.Description; | 5 | import org.onlab.onos.net.Description; |
| 4 | import org.onlab.onos.net.HostLocation; | 6 | import org.onlab.onos.net.HostLocation; |
| 5 | import org.onlab.packet.IpPrefix; | 7 | import org.onlab.packet.IpPrefix; |
| ... | @@ -38,6 +40,6 @@ public interface HostDescription extends Description { | ... | @@ -38,6 +40,6 @@ public interface HostDescription extends Description { |
| 38 | * @return host IP address | 40 | * @return host IP address |
| 39 | */ | 41 | */ |
| 40 | // FIXME: Switch to IpAddress | 42 | // FIXME: Switch to IpAddress |
| 41 | - IpPrefix ipAddress(); | 43 | + Set<IpPrefix> ipAddress(); |
| 42 | 44 | ||
| 43 | } | 45 | } | ... | ... |
| ... | @@ -8,6 +8,8 @@ import org.onlab.packet.IpPrefix; | ... | @@ -8,6 +8,8 @@ import org.onlab.packet.IpPrefix; |
| 8 | import org.onlab.packet.MacAddress; | 8 | import org.onlab.packet.MacAddress; |
| 9 | import org.onlab.packet.VlanId; | 9 | import org.onlab.packet.VlanId; |
| 10 | 10 | ||
| 11 | +import com.google.common.collect.ImmutableSet; | ||
| 12 | + | ||
| 11 | import static org.junit.Assert.assertEquals; | 13 | import static org.junit.Assert.assertEquals; |
| 12 | import static org.junit.Assert.assertTrue; | 14 | import static org.junit.Assert.assertTrue; |
| 13 | 15 | ||
| ... | @@ -33,7 +35,7 @@ public class DefualtHostDecriptionTest { | ... | @@ -33,7 +35,7 @@ public class DefualtHostDecriptionTest { |
| 33 | assertEquals("incorrect mac", MAC, host.hwAddress()); | 35 | assertEquals("incorrect mac", MAC, host.hwAddress()); |
| 34 | assertEquals("incorrect vlan", VLAN, host.vlan()); | 36 | assertEquals("incorrect vlan", VLAN, host.vlan()); |
| 35 | assertEquals("incorrect location", LOC, host.location()); | 37 | assertEquals("incorrect location", LOC, host.location()); |
| 36 | - assertEquals("incorrect ip's", IP, host.ipAddress()); | 38 | + assertEquals("incorrect ip's", ImmutableSet.of(IP), host.ipAddress()); |
| 37 | assertTrue("incorrect toString", host.toString().contains("vlan=10")); | 39 | assertTrue("incorrect toString", host.toString().contains("vlan=10")); |
| 38 | } | 40 | } |
| 39 | 41 | ... | ... |
| 1 | package org.onlab.onos.store.host.impl; | 1 | package org.onlab.onos.store.host.impl; |
| 2 | 2 | ||
| 3 | +import com.google.common.collect.FluentIterable; | ||
| 3 | import com.google.common.collect.HashMultimap; | 4 | import com.google.common.collect.HashMultimap; |
| 5 | +import com.google.common.collect.ImmutableList; | ||
| 4 | import com.google.common.collect.ImmutableSet; | 6 | import com.google.common.collect.ImmutableSet; |
| 5 | import com.google.common.collect.Multimap; | 7 | import com.google.common.collect.Multimap; |
| 6 | import com.google.common.collect.Sets; | 8 | import com.google.common.collect.Sets; |
| 7 | 9 | ||
| 10 | +import org.apache.commons.lang3.RandomUtils; | ||
| 8 | import org.apache.felix.scr.annotations.Activate; | 11 | import org.apache.felix.scr.annotations.Activate; |
| 9 | import org.apache.felix.scr.annotations.Component; | 12 | import org.apache.felix.scr.annotations.Component; |
| 10 | import org.apache.felix.scr.annotations.Deactivate; | 13 | import org.apache.felix.scr.annotations.Deactivate; |
| ... | @@ -12,6 +15,8 @@ import org.apache.felix.scr.annotations.Reference; | ... | @@ -12,6 +15,8 @@ import org.apache.felix.scr.annotations.Reference; |
| 12 | import org.apache.felix.scr.annotations.ReferenceCardinality; | 15 | import org.apache.felix.scr.annotations.ReferenceCardinality; |
| 13 | import org.apache.felix.scr.annotations.Service; | 16 | import org.apache.felix.scr.annotations.Service; |
| 14 | import org.onlab.onos.cluster.ClusterService; | 17 | import org.onlab.onos.cluster.ClusterService; |
| 18 | +import org.onlab.onos.cluster.ControllerNode; | ||
| 19 | +import org.onlab.onos.cluster.NodeId; | ||
| 15 | import org.onlab.onos.net.Annotations; | 20 | import org.onlab.onos.net.Annotations; |
| 16 | import org.onlab.onos.net.ConnectPoint; | 21 | import org.onlab.onos.net.ConnectPoint; |
| 17 | import org.onlab.onos.net.DefaultHost; | 22 | import org.onlab.onos.net.DefaultHost; |
| ... | @@ -19,6 +24,7 @@ import org.onlab.onos.net.DeviceId; | ... | @@ -19,6 +24,7 @@ import org.onlab.onos.net.DeviceId; |
| 19 | import org.onlab.onos.net.Host; | 24 | import org.onlab.onos.net.Host; |
| 20 | import org.onlab.onos.net.HostId; | 25 | import org.onlab.onos.net.HostId; |
| 21 | import org.onlab.onos.net.HostLocation; | 26 | import org.onlab.onos.net.HostLocation; |
| 27 | +import org.onlab.onos.net.host.DefaultHostDescription; | ||
| 22 | import org.onlab.onos.net.host.HostClockService; | 28 | import org.onlab.onos.net.host.HostClockService; |
| 23 | import org.onlab.onos.net.host.HostDescription; | 29 | import org.onlab.onos.net.host.HostDescription; |
| 24 | import org.onlab.onos.net.host.HostEvent; | 30 | import org.onlab.onos.net.host.HostEvent; |
| ... | @@ -42,12 +48,19 @@ import org.onlab.util.KryoPool; | ... | @@ -42,12 +48,19 @@ import org.onlab.util.KryoPool; |
| 42 | import org.slf4j.Logger; | 48 | import org.slf4j.Logger; |
| 43 | 49 | ||
| 44 | import java.io.IOException; | 50 | import java.io.IOException; |
| 51 | +import java.util.HashMap; | ||
| 45 | import java.util.HashSet; | 52 | import java.util.HashSet; |
| 46 | import java.util.Map; | 53 | import java.util.Map; |
| 47 | import java.util.Set; | 54 | import java.util.Set; |
| 55 | +import java.util.Map.Entry; | ||
| 48 | import java.util.concurrent.ConcurrentHashMap; | 56 | import java.util.concurrent.ConcurrentHashMap; |
| 57 | +import java.util.concurrent.ScheduledExecutorService; | ||
| 58 | +import java.util.concurrent.TimeUnit; | ||
| 49 | 59 | ||
| 60 | +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; | ||
| 61 | +import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId; | ||
| 50 | import static org.onlab.onos.net.host.HostEvent.Type.*; | 62 | import static org.onlab.onos.net.host.HostEvent.Type.*; |
| 63 | +import static org.onlab.util.Tools.namedThreads; | ||
| 51 | import static org.slf4j.LoggerFactory.getLogger; | 64 | import static org.slf4j.LoggerFactory.getLogger; |
| 52 | 65 | ||
| 53 | //TODO: multi-provider, annotation not supported. | 66 | //TODO: multi-provider, annotation not supported. |
| ... | @@ -88,24 +101,58 @@ public class GossipHostStore | ... | @@ -88,24 +101,58 @@ public class GossipHostStore |
| 88 | protected void setupKryoPool() { | 101 | protected void setupKryoPool() { |
| 89 | serializerPool = KryoPool.newBuilder() | 102 | serializerPool = KryoPool.newBuilder() |
| 90 | .register(DistributedStoreSerializers.COMMON) | 103 | .register(DistributedStoreSerializers.COMMON) |
| 104 | + .register(InternalHostEvent.class) | ||
| 91 | .register(InternalHostRemovedEvent.class) | 105 | .register(InternalHostRemovedEvent.class) |
| 106 | + .register(HostFragmentId.class) | ||
| 107 | + .register(HostAntiEntropyAdvertisement.class) | ||
| 92 | .build() | 108 | .build() |
| 93 | .populate(1); | 109 | .populate(1); |
| 94 | } | 110 | } |
| 95 | }; | 111 | }; |
| 96 | 112 | ||
| 113 | + private ScheduledExecutorService executor; | ||
| 114 | + | ||
| 97 | @Activate | 115 | @Activate |
| 98 | public void activate() { | 116 | public void activate() { |
| 99 | clusterCommunicator.addSubscriber( | 117 | clusterCommunicator.addSubscriber( |
| 100 | - GossipHostStoreMessageSubjects.HOST_UPDATED, new InternalHostEventListener()); | 118 | + GossipHostStoreMessageSubjects.HOST_UPDATED, |
| 119 | + new InternalHostEventListener()); | ||
| 120 | + clusterCommunicator.addSubscriber( | ||
| 121 | + GossipHostStoreMessageSubjects.HOST_REMOVED, | ||
| 122 | + new InternalHostRemovedEventListener()); | ||
| 101 | clusterCommunicator.addSubscriber( | 123 | clusterCommunicator.addSubscriber( |
| 102 | - GossipHostStoreMessageSubjects.HOST_REMOVED, new InternalHostRemovedEventListener()); | 124 | + GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT, |
| 125 | + new InternalHostAntiEntropyAdvertisementListener()); | ||
| 126 | + | ||
| 127 | + executor = | ||
| 128 | + newSingleThreadScheduledExecutor(namedThreads("link-anti-entropy-%d")); | ||
| 129 | + | ||
| 130 | + // TODO: Make these configurable | ||
| 131 | + long initialDelaySec = 5; | ||
| 132 | + long periodSec = 5; | ||
| 133 | + // start anti-entropy thread | ||
| 134 | + executor.scheduleAtFixedRate(new SendAdvertisementTask(), | ||
| 135 | + initialDelaySec, periodSec, TimeUnit.SECONDS); | ||
| 103 | 136 | ||
| 104 | log.info("Started"); | 137 | log.info("Started"); |
| 105 | } | 138 | } |
| 106 | 139 | ||
| 107 | @Deactivate | 140 | @Deactivate |
| 108 | public void deactivate() { | 141 | public void deactivate() { |
| 142 | + executor.shutdownNow(); | ||
| 143 | + try { | ||
| 144 | + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { | ||
| 145 | + log.error("Timeout during executor shutdown"); | ||
| 146 | + } | ||
| 147 | + } catch (InterruptedException e) { | ||
| 148 | + log.error("Error during executor shutdown", e); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + hosts.clear(); | ||
| 152 | + removedHosts.clear(); | ||
| 153 | + locations.clear(); | ||
| 154 | + portAddresses.clear(); | ||
| 155 | + | ||
| 109 | log.info("Stopped"); | 156 | log.info("Stopped"); |
| 110 | } | 157 | } |
| 111 | 158 | ||
| ... | @@ -153,7 +200,7 @@ public class GossipHostStore | ... | @@ -153,7 +200,7 @@ public class GossipHostStore |
| 153 | descr.hwAddress(), | 200 | descr.hwAddress(), |
| 154 | descr.vlan(), | 201 | descr.vlan(), |
| 155 | new Timestamped<>(descr.location(), timestamp), | 202 | new Timestamped<>(descr.location(), timestamp), |
| 156 | - ImmutableSet.of(descr.ipAddress())); | 203 | + ImmutableSet.copyOf(descr.ipAddress())); |
| 157 | hosts.put(hostId, newhost); | 204 | hosts.put(hostId, newhost); |
| 158 | locations.put(descr.location(), newhost); | 205 | locations.put(descr.location(), newhost); |
| 159 | return new HostEvent(HOST_ADDED, newhost); | 206 | return new HostEvent(HOST_ADDED, newhost); |
| ... | @@ -169,12 +216,12 @@ public class GossipHostStore | ... | @@ -169,12 +216,12 @@ public class GossipHostStore |
| 169 | return new HostEvent(HOST_MOVED, host); | 216 | return new HostEvent(HOST_MOVED, host); |
| 170 | } | 217 | } |
| 171 | 218 | ||
| 172 | - if (host.ipAddresses().contains(descr.ipAddress())) { | 219 | + if (host.ipAddresses().containsAll(descr.ipAddress())) { |
| 173 | return null; | 220 | return null; |
| 174 | } | 221 | } |
| 175 | 222 | ||
| 176 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); | 223 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); |
| 177 | - addresses.add(descr.ipAddress()); | 224 | + addresses.addAll(descr.ipAddress()); |
| 178 | StoredHost updated = new StoredHost(providerId, host.id(), | 225 | StoredHost updated = new StoredHost(providerId, host.id(), |
| 179 | host.mac(), host.vlan(), | 226 | host.mac(), host.vlan(), |
| 180 | host.location, addresses); | 227 | host.location, addresses); |
| ... | @@ -381,6 +428,10 @@ public class GossipHostStore | ... | @@ -381,6 +428,10 @@ public class GossipHostStore |
| 381 | public HostLocation location() { | 428 | public HostLocation location() { |
| 382 | return location.value(); | 429 | return location.value(); |
| 383 | } | 430 | } |
| 431 | + | ||
| 432 | + public Timestamp timestamp() { | ||
| 433 | + return location.timestamp(); | ||
| 434 | + } | ||
| 384 | } | 435 | } |
| 385 | 436 | ||
| 386 | private void notifyPeers(InternalHostRemovedEvent event) throws IOException { | 437 | private void notifyPeers(InternalHostRemovedEvent event) throws IOException { |
| ... | @@ -399,6 +450,16 @@ public class GossipHostStore | ... | @@ -399,6 +450,16 @@ public class GossipHostStore |
| 399 | clusterCommunicator.broadcast(message); | 450 | clusterCommunicator.broadcast(message); |
| 400 | } | 451 | } |
| 401 | 452 | ||
| 453 | + private void unicastMessage(NodeId peer, | ||
| 454 | + MessageSubject subject, | ||
| 455 | + Object event) throws IOException { | ||
| 456 | + ClusterMessage message = new ClusterMessage( | ||
| 457 | + clusterService.getLocalNode().id(), | ||
| 458 | + subject, | ||
| 459 | + SERIALIZER.encode(event)); | ||
| 460 | + clusterCommunicator.unicast(message, peer); | ||
| 461 | + } | ||
| 462 | + | ||
| 402 | private void notifyDelegateIfNotNull(HostEvent event) { | 463 | private void notifyDelegateIfNotNull(HostEvent event) { |
| 403 | if (event != null) { | 464 | if (event != null) { |
| 404 | notifyDelegate(event); | 465 | notifyDelegate(event); |
| ... | @@ -434,4 +495,165 @@ public class GossipHostStore | ... | @@ -434,4 +495,165 @@ public class GossipHostStore |
| 434 | notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp)); | 495 | notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp)); |
| 435 | } | 496 | } |
| 436 | } | 497 | } |
| 498 | + | ||
| 499 | + private final class SendAdvertisementTask implements Runnable { | ||
| 500 | + | ||
| 501 | + @Override | ||
| 502 | + public void run() { | ||
| 503 | + if (Thread.currentThread().isInterrupted()) { | ||
| 504 | + log.info("Interrupted, quitting"); | ||
| 505 | + return; | ||
| 506 | + } | ||
| 507 | + | ||
| 508 | + try { | ||
| 509 | + final NodeId self = clusterService.getLocalNode().id(); | ||
| 510 | + Set<ControllerNode> nodes = clusterService.getNodes(); | ||
| 511 | + | ||
| 512 | + ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes) | ||
| 513 | + .transform(toNodeId()) | ||
| 514 | + .toList(); | ||
| 515 | + | ||
| 516 | + if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) { | ||
| 517 | + log.debug("No other peers in the cluster."); | ||
| 518 | + return; | ||
| 519 | + } | ||
| 520 | + | ||
| 521 | + NodeId peer; | ||
| 522 | + do { | ||
| 523 | + int idx = RandomUtils.nextInt(0, nodeIds.size()); | ||
| 524 | + peer = nodeIds.get(idx); | ||
| 525 | + } while (peer.equals(self)); | ||
| 526 | + | ||
| 527 | + HostAntiEntropyAdvertisement ad = createAdvertisement(); | ||
| 528 | + | ||
| 529 | + if (Thread.currentThread().isInterrupted()) { | ||
| 530 | + log.info("Interrupted, quitting"); | ||
| 531 | + return; | ||
| 532 | + } | ||
| 533 | + | ||
| 534 | + try { | ||
| 535 | + unicastMessage(peer, GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT, ad); | ||
| 536 | + } catch (IOException e) { | ||
| 537 | + log.debug("Failed to send anti-entropy advertisement", e); | ||
| 538 | + return; | ||
| 539 | + } | ||
| 540 | + } catch (Exception e) { | ||
| 541 | + // catch all Exception to avoid Scheduled task being suppressed. | ||
| 542 | + log.error("Exception thrown while sending advertisement", e); | ||
| 543 | + } | ||
| 544 | + } | ||
| 545 | + } | ||
| 546 | + | ||
| 547 | + private HostAntiEntropyAdvertisement createAdvertisement() { | ||
| 548 | + final NodeId self = clusterService.getLocalNode().id(); | ||
| 549 | + | ||
| 550 | + Map<HostFragmentId, Timestamp> timestamps = new HashMap<>(hosts.size()); | ||
| 551 | + Map<HostId, Timestamp> tombstones = new HashMap<>(removedHosts.size()); | ||
| 552 | + | ||
| 553 | + for (Entry<HostId, StoredHost> e : hosts.entrySet()) { | ||
| 554 | + | ||
| 555 | + final HostId hostId = e.getKey(); | ||
| 556 | + final StoredHost hostInfo = e.getValue(); | ||
| 557 | + final ProviderId providerId = hostInfo.providerId(); | ||
| 558 | + timestamps.put(new HostFragmentId(hostId, providerId), hostInfo.timestamp()); | ||
| 559 | + } | ||
| 560 | + | ||
| 561 | + for (Entry<HostId, Timestamped<Host>> e : removedHosts.entrySet()) { | ||
| 562 | + tombstones.put(e.getKey(), e.getValue().timestamp()); | ||
| 563 | + } | ||
| 564 | + | ||
| 565 | + return new HostAntiEntropyAdvertisement(self, timestamps, tombstones); | ||
| 566 | + } | ||
| 567 | + | ||
| 568 | + private synchronized void handleAntiEntropyAdvertisement(HostAntiEntropyAdvertisement ad) { | ||
| 569 | + | ||
| 570 | + final NodeId sender = ad.sender(); | ||
| 571 | + | ||
| 572 | + for (Entry<HostId, StoredHost> host : hosts.entrySet()) { | ||
| 573 | + // for each locally live Hosts... | ||
| 574 | + final HostId hostId = host.getKey(); | ||
| 575 | + final StoredHost localHost = host.getValue(); | ||
| 576 | + final ProviderId providerId = localHost.providerId(); | ||
| 577 | + final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId); | ||
| 578 | + final Timestamp localLiveTimestamp = localHost.timestamp(); | ||
| 579 | + | ||
| 580 | + Timestamp remoteTimestamp = ad.timestamps().get(hostFragId); | ||
| 581 | + if (remoteTimestamp == null) { | ||
| 582 | + remoteTimestamp = ad.tombstones().get(hostId); | ||
| 583 | + } | ||
| 584 | + if (remoteTimestamp == null || | ||
| 585 | + localLiveTimestamp.compareTo(remoteTimestamp) > 0) { | ||
| 586 | + | ||
| 587 | + // local is more recent, push | ||
| 588 | + // TODO: annotation is lost | ||
| 589 | + final HostDescription desc = new DefaultHostDescription( | ||
| 590 | + localHost.mac(), | ||
| 591 | + localHost.vlan(), | ||
| 592 | + localHost.location(), | ||
| 593 | + localHost.ipAddresses()); | ||
| 594 | + try { | ||
| 595 | + unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_UPDATED, | ||
| 596 | + new InternalHostEvent(providerId, hostId, desc, localHost.timestamp())); | ||
| 597 | + } catch (IOException e1) { | ||
| 598 | + log.debug("Failed to send advertisement response", e1); | ||
| 599 | + } | ||
| 600 | + } | ||
| 601 | + | ||
| 602 | + final Timestamp remoteDeadTimestamp = ad.tombstones().get(hostId); | ||
| 603 | + if (remoteDeadTimestamp != null && | ||
| 604 | + remoteDeadTimestamp.compareTo(localLiveTimestamp) > 0) { | ||
| 605 | + // sender has recent remove | ||
| 606 | + notifyDelegateIfNotNull(removeHostInternal(hostId, remoteDeadTimestamp)); | ||
| 607 | + } | ||
| 608 | + } | ||
| 609 | + | ||
| 610 | + for (Entry<HostId, Timestamped<Host>> dead : removedHosts.entrySet()) { | ||
| 611 | + // for each locally dead Hosts | ||
| 612 | + final HostId hostId = dead.getKey(); | ||
| 613 | + final Timestamp localDeadTimestamp = dead.getValue().timestamp(); | ||
| 614 | + | ||
| 615 | + // TODO: pick proper ProviderId, when supporting multi-provider | ||
| 616 | + final ProviderId providerId = dead.getValue().value().providerId(); | ||
| 617 | + final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId); | ||
| 618 | + | ||
| 619 | + final Timestamp remoteLiveTimestamp = ad.timestamps().get(hostFragId); | ||
| 620 | + if (remoteLiveTimestamp != null && | ||
| 621 | + localDeadTimestamp.compareTo(remoteLiveTimestamp) > 0) { | ||
| 622 | + // sender has zombie, push | ||
| 623 | + try { | ||
| 624 | + unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_REMOVED, | ||
| 625 | + new InternalHostRemovedEvent(hostId, localDeadTimestamp)); | ||
| 626 | + } catch (IOException e1) { | ||
| 627 | + log.debug("Failed to send advertisement response", e1); | ||
| 628 | + } | ||
| 629 | + } | ||
| 630 | + } | ||
| 631 | + | ||
| 632 | + | ||
| 633 | + for (Entry<HostId, Timestamp> e : ad.tombstones().entrySet()) { | ||
| 634 | + // for each remote tombstone advertisement... | ||
| 635 | + final HostId hostId = e.getKey(); | ||
| 636 | + final Timestamp adRemoveTimestamp = e.getValue(); | ||
| 637 | + | ||
| 638 | + final StoredHost storedHost = hosts.get(hostId); | ||
| 639 | + if (storedHost == null) { | ||
| 640 | + continue; | ||
| 641 | + } | ||
| 642 | + if (adRemoveTimestamp.compareTo(storedHost.timestamp()) > 0) { | ||
| 643 | + // sender has recent remove info, locally remove | ||
| 644 | + notifyDelegateIfNotNull(removeHostInternal(hostId, adRemoveTimestamp)); | ||
| 645 | + } | ||
| 646 | + } | ||
| 647 | + } | ||
| 648 | + | ||
| 649 | + private final class InternalHostAntiEntropyAdvertisementListener implements | ||
| 650 | + ClusterMessageHandler { | ||
| 651 | + | ||
| 652 | + @Override | ||
| 653 | + public void handle(ClusterMessage message) { | ||
| 654 | + log.debug("Received Host Anti-Entropy advertisement from peer: {}", message.sender()); | ||
| 655 | + HostAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload()); | ||
| 656 | + handleAntiEntropyAdvertisement(advertisement); | ||
| 657 | + } | ||
| 658 | + } | ||
| 437 | } | 659 | } | ... | ... |
| ... | @@ -4,6 +4,11 @@ import org.onlab.onos.store.cluster.messaging.MessageSubject; | ... | @@ -4,6 +4,11 @@ import org.onlab.onos.store.cluster.messaging.MessageSubject; |
| 4 | 4 | ||
| 5 | public final class GossipHostStoreMessageSubjects { | 5 | public final class GossipHostStoreMessageSubjects { |
| 6 | private GossipHostStoreMessageSubjects() {} | 6 | private GossipHostStoreMessageSubjects() {} |
| 7 | - public static final MessageSubject HOST_UPDATED = new MessageSubject("peer-host-updated"); | 7 | + |
| 8 | - public static final MessageSubject HOST_REMOVED = new MessageSubject("peer-host-removed"); | 8 | + public static final MessageSubject HOST_UPDATED |
| 9 | + = new MessageSubject("peer-host-updated"); | ||
| 10 | + public static final MessageSubject HOST_REMOVED | ||
| 11 | + = new MessageSubject("peer-host-removed"); | ||
| 12 | + public static final MessageSubject HOST_ANTI_ENTROPY_ADVERTISEMENT | ||
| 13 | + = new MessageSubject("host-enti-entropy-advertisement");; | ||
| 9 | } | 14 | } | ... | ... |
core/store/dist/src/main/java/org/onlab/onos/store/host/impl/HostAntiEntropyAdvertisement.java
0 → 100644
| 1 | +package org.onlab.onos.store.host.impl; | ||
| 2 | + | ||
| 3 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
| 4 | + | ||
| 5 | +import java.util.Map; | ||
| 6 | + | ||
| 7 | +import org.onlab.onos.cluster.NodeId; | ||
| 8 | +import org.onlab.onos.net.HostId; | ||
| 9 | +import org.onlab.onos.store.Timestamp; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Host AE Advertisement message. | ||
| 13 | + */ | ||
| 14 | +public final class HostAntiEntropyAdvertisement { | ||
| 15 | + | ||
| 16 | + private final NodeId sender; | ||
| 17 | + private final Map<HostFragmentId, Timestamp> timestamps; | ||
| 18 | + private final Map<HostId, Timestamp> tombstones; | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + public HostAntiEntropyAdvertisement(NodeId sender, | ||
| 22 | + Map<HostFragmentId, Timestamp> timestamps, | ||
| 23 | + Map<HostId, Timestamp> tombstones) { | ||
| 24 | + this.sender = checkNotNull(sender); | ||
| 25 | + this.timestamps = checkNotNull(timestamps); | ||
| 26 | + this.tombstones = checkNotNull(tombstones); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public NodeId sender() { | ||
| 30 | + return sender; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public Map<HostFragmentId, Timestamp> timestamps() { | ||
| 34 | + return timestamps; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public Map<HostId, Timestamp> tombstones() { | ||
| 38 | + return tombstones; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + // For serializer | ||
| 42 | + @SuppressWarnings("unused") | ||
| 43 | + private HostAntiEntropyAdvertisement() { | ||
| 44 | + this.sender = null; | ||
| 45 | + this.timestamps = null; | ||
| 46 | + this.tombstones = null; | ||
| 47 | + } | ||
| 48 | +} |
| 1 | +package org.onlab.onos.store.host.impl; | ||
| 2 | + | ||
| 3 | +import java.util.Objects; | ||
| 4 | + | ||
| 5 | +import org.onlab.onos.net.HostId; | ||
| 6 | +import org.onlab.onos.net.provider.ProviderId; | ||
| 7 | + | ||
| 8 | +import com.google.common.base.MoreObjects; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * Identifier for HostDescription from a Provider. | ||
| 12 | + */ | ||
| 13 | +public final class HostFragmentId { | ||
| 14 | + public final ProviderId providerId; | ||
| 15 | + public final HostId hostId; | ||
| 16 | + | ||
| 17 | + public HostFragmentId(HostId hostId, ProviderId providerId) { | ||
| 18 | + this.providerId = providerId; | ||
| 19 | + this.hostId = hostId; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + public HostId hostId() { | ||
| 23 | + return hostId; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + public ProviderId providerId() { | ||
| 27 | + return providerId; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @Override | ||
| 31 | + public int hashCode() { | ||
| 32 | + return Objects.hash(providerId, hostId); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Override | ||
| 36 | + public boolean equals(Object obj) { | ||
| 37 | + if (this == obj) { | ||
| 38 | + return true; | ||
| 39 | + } | ||
| 40 | + if (!(obj instanceof HostFragmentId)) { | ||
| 41 | + return false; | ||
| 42 | + } | ||
| 43 | + HostFragmentId that = (HostFragmentId) obj; | ||
| 44 | + return Objects.equals(this.hostId, that.hostId) && | ||
| 45 | + Objects.equals(this.providerId, that.providerId); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public String toString() { | ||
| 50 | + return MoreObjects.toStringHelper(getClass()) | ||
| 51 | + .add("providerId", providerId) | ||
| 52 | + .add("hostId", hostId) | ||
| 53 | + .toString(); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + // for serializer | ||
| 57 | + @SuppressWarnings("unused") | ||
| 58 | + private HostFragmentId() { | ||
| 59 | + this.providerId = null; | ||
| 60 | + this.hostId = null; | ||
| 61 | + } | ||
| 62 | +} |
| ... | @@ -84,7 +84,7 @@ public class DistributedHostStore | ... | @@ -84,7 +84,7 @@ public class DistributedHostStore |
| 84 | descr.hwAddress(), | 84 | descr.hwAddress(), |
| 85 | descr.vlan(), | 85 | descr.vlan(), |
| 86 | descr.location(), | 86 | descr.location(), |
| 87 | - ImmutableSet.of(descr.ipAddress())); | 87 | + ImmutableSet.copyOf(descr.ipAddress())); |
| 88 | synchronized (this) { | 88 | synchronized (this) { |
| 89 | hosts.put(hostId, newhost); | 89 | hosts.put(hostId, newhost); |
| 90 | locations.put(descr.location(), newhost); | 90 | locations.put(descr.location(), newhost); |
| ... | @@ -101,12 +101,12 @@ public class DistributedHostStore | ... | @@ -101,12 +101,12 @@ public class DistributedHostStore |
| 101 | return new HostEvent(HOST_MOVED, host); | 101 | return new HostEvent(HOST_MOVED, host); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | - if (host.ipAddresses().contains(descr.ipAddress())) { | 104 | + if (host.ipAddresses().containsAll(descr.ipAddress())) { |
| 105 | return null; | 105 | return null; |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); | 108 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); |
| 109 | - addresses.add(descr.ipAddress()); | 109 | + addresses.addAll(descr.ipAddress()); |
| 110 | StoredHost updated = new StoredHost(providerId, host.id(), | 110 | StoredHost updated = new StoredHost(providerId, host.id(), |
| 111 | host.mac(), host.vlan(), | 111 | host.mac(), host.vlan(), |
| 112 | descr.location(), addresses); | 112 | descr.location(), addresses); | ... | ... |
core/store/serializers/src/main/java/org/onlab/onos/store/serializers/HostLocationSerializer.java
0 → 100644
| 1 | +package org.onlab.onos.store.serializers; | ||
| 2 | + | ||
| 3 | +import org.onlab.onos.net.DeviceId; | ||
| 4 | +import org.onlab.onos.net.HostLocation; | ||
| 5 | +import org.onlab.onos.net.PortNumber; | ||
| 6 | + | ||
| 7 | +import com.esotericsoftware.kryo.Kryo; | ||
| 8 | +import com.esotericsoftware.kryo.Serializer; | ||
| 9 | +import com.esotericsoftware.kryo.io.Input; | ||
| 10 | +import com.esotericsoftware.kryo.io.Output; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | +* Kryo Serializer for {@link HostLocation}. | ||
| 14 | +*/ | ||
| 15 | +public class HostLocationSerializer extends Serializer<HostLocation> { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * Creates {@link HostLocation} serializer instance. | ||
| 19 | + */ | ||
| 20 | + public HostLocationSerializer() { | ||
| 21 | + // non-null, immutable | ||
| 22 | + super(false, true); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + @Override | ||
| 26 | + public void write(Kryo kryo, Output output, HostLocation object) { | ||
| 27 | + kryo.writeClassAndObject(output, object.deviceId()); | ||
| 28 | + kryo.writeClassAndObject(output, object.port()); | ||
| 29 | + output.writeLong(object.time()); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @Override | ||
| 33 | + public HostLocation read(Kryo kryo, Input input, Class<HostLocation> type) { | ||
| 34 | + DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input); | ||
| 35 | + PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input); | ||
| 36 | + long time = input.readLong(); | ||
| 37 | + return new HostLocation(deviceId, portNumber, time); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | +} |
| ... | @@ -17,6 +17,8 @@ import org.onlab.onos.net.DefaultPort; | ... | @@ -17,6 +17,8 @@ import org.onlab.onos.net.DefaultPort; |
| 17 | import org.onlab.onos.net.Device; | 17 | import org.onlab.onos.net.Device; |
| 18 | import org.onlab.onos.net.DeviceId; | 18 | import org.onlab.onos.net.DeviceId; |
| 19 | import org.onlab.onos.net.Element; | 19 | import org.onlab.onos.net.Element; |
| 20 | +import org.onlab.onos.net.HostId; | ||
| 21 | +import org.onlab.onos.net.HostLocation; | ||
| 20 | import org.onlab.onos.net.Link; | 22 | import org.onlab.onos.net.Link; |
| 21 | import org.onlab.onos.net.LinkKey; | 23 | import org.onlab.onos.net.LinkKey; |
| 22 | import org.onlab.onos.net.MastershipRole; | 24 | import org.onlab.onos.net.MastershipRole; |
| ... | @@ -24,15 +26,20 @@ import org.onlab.onos.net.Port; | ... | @@ -24,15 +26,20 @@ import org.onlab.onos.net.Port; |
| 24 | import org.onlab.onos.net.PortNumber; | 26 | import org.onlab.onos.net.PortNumber; |
| 25 | import org.onlab.onos.net.device.DefaultDeviceDescription; | 27 | import org.onlab.onos.net.device.DefaultDeviceDescription; |
| 26 | import org.onlab.onos.net.device.DefaultPortDescription; | 28 | import org.onlab.onos.net.device.DefaultPortDescription; |
| 29 | +import org.onlab.onos.net.host.DefaultHostDescription; | ||
| 30 | +import org.onlab.onos.net.host.HostDescription; | ||
| 27 | import org.onlab.onos.net.link.DefaultLinkDescription; | 31 | import org.onlab.onos.net.link.DefaultLinkDescription; |
| 28 | import org.onlab.onos.net.provider.ProviderId; | 32 | import org.onlab.onos.net.provider.ProviderId; |
| 29 | import org.onlab.onos.store.Timestamp; | 33 | import org.onlab.onos.store.Timestamp; |
| 30 | import org.onlab.packet.IpAddress; | 34 | import org.onlab.packet.IpAddress; |
| 31 | import org.onlab.packet.IpPrefix; | 35 | import org.onlab.packet.IpPrefix; |
| 36 | +import org.onlab.packet.MacAddress; | ||
| 37 | +import org.onlab.packet.VlanId; | ||
| 32 | import org.onlab.util.KryoPool; | 38 | import org.onlab.util.KryoPool; |
| 33 | 39 | ||
| 34 | import com.google.common.collect.ImmutableList; | 40 | import com.google.common.collect.ImmutableList; |
| 35 | import com.google.common.collect.ImmutableMap; | 41 | import com.google.common.collect.ImmutableMap; |
| 42 | +import com.google.common.collect.ImmutableSet; | ||
| 36 | 43 | ||
| 37 | public final class KryoPoolUtil { | 44 | public final class KryoPoolUtil { |
| 38 | 45 | ||
| ... | @@ -42,6 +49,8 @@ public final class KryoPoolUtil { | ... | @@ -42,6 +49,8 @@ public final class KryoPoolUtil { |
| 42 | public static final KryoPool MISC = KryoPool.newBuilder() | 49 | public static final KryoPool MISC = KryoPool.newBuilder() |
| 43 | .register(IpPrefix.class, new IpPrefixSerializer()) | 50 | .register(IpPrefix.class, new IpPrefixSerializer()) |
| 44 | .register(IpAddress.class, new IpAddressSerializer()) | 51 | .register(IpAddress.class, new IpAddressSerializer()) |
| 52 | + .register(MacAddress.class, new MacAddressSerializer()) | ||
| 53 | + .register(VlanId.class) | ||
| 45 | .build(); | 54 | .build(); |
| 46 | 55 | ||
| 47 | // TODO: Populate other classes | 56 | // TODO: Populate other classes |
| ... | @@ -52,6 +61,7 @@ public final class KryoPoolUtil { | ... | @@ -52,6 +61,7 @@ public final class KryoPoolUtil { |
| 52 | .register(MISC) | 61 | .register(MISC) |
| 53 | .register(ImmutableMap.class, new ImmutableMapSerializer()) | 62 | .register(ImmutableMap.class, new ImmutableMapSerializer()) |
| 54 | .register(ImmutableList.class, new ImmutableListSerializer()) | 63 | .register(ImmutableList.class, new ImmutableListSerializer()) |
| 64 | + .register(ImmutableSet.class, new ImmutableSetSerializer()) | ||
| 55 | .register( | 65 | .register( |
| 56 | // | 66 | // |
| 57 | ArrayList.class, | 67 | ArrayList.class, |
| ... | @@ -71,8 +81,10 @@ public final class KryoPoolUtil { | ... | @@ -71,8 +81,10 @@ public final class KryoPoolUtil { |
| 71 | DefaultPortDescription.class, | 81 | DefaultPortDescription.class, |
| 72 | Element.class, | 82 | Element.class, |
| 73 | Link.Type.class, | 83 | Link.Type.class, |
| 74 | - Timestamp.class | 84 | + Timestamp.class, |
| 75 | - | 85 | + HostId.class, |
| 86 | + HostDescription.class, | ||
| 87 | + DefaultHostDescription.class | ||
| 76 | ) | 88 | ) |
| 77 | .register(URI.class, new URISerializer()) | 89 | .register(URI.class, new URISerializer()) |
| 78 | .register(NodeId.class, new NodeIdSerializer()) | 90 | .register(NodeId.class, new NodeIdSerializer()) |
| ... | @@ -85,6 +97,7 @@ public final class KryoPoolUtil { | ... | @@ -85,6 +97,7 @@ public final class KryoPoolUtil { |
| 85 | .register(DefaultLink.class, new DefaultLinkSerializer()) | 97 | .register(DefaultLink.class, new DefaultLinkSerializer()) |
| 86 | .register(MastershipTerm.class, new MastershipTermSerializer()) | 98 | .register(MastershipTerm.class, new MastershipTermSerializer()) |
| 87 | .register(MastershipRole.class, new MastershipRoleSerializer()) | 99 | .register(MastershipRole.class, new MastershipRoleSerializer()) |
| 100 | + .register(HostLocation.class, new HostLocationSerializer()) | ||
| 88 | 101 | ||
| 89 | .build(); | 102 | .build(); |
| 90 | 103 | ... | ... |
core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
0 → 100644
| 1 | +package org.onlab.onos.store.serializers; | ||
| 2 | + | ||
| 3 | +import org.onlab.packet.MacAddress; | ||
| 4 | + | ||
| 5 | +import com.esotericsoftware.kryo.Kryo; | ||
| 6 | +import com.esotericsoftware.kryo.Serializer; | ||
| 7 | +import com.esotericsoftware.kryo.io.Input; | ||
| 8 | +import com.esotericsoftware.kryo.io.Output; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * Kryo Serializer for {@link MacAddress}. | ||
| 12 | + */ | ||
| 13 | +public class MacAddressSerializer extends Serializer<MacAddress> { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * Creates {@link MacAddress} serializer instance. | ||
| 17 | + */ | ||
| 18 | + public MacAddressSerializer() { | ||
| 19 | + super(false, true); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + @Override | ||
| 23 | + public void write(Kryo kryo, Output output, MacAddress object) { | ||
| 24 | + output.writeBytes(object.getAddress()); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + @Override | ||
| 28 | + public MacAddress read(Kryo kryo, Input input, Class<MacAddress> type) { | ||
| 29 | + return MacAddress.valueOf(input.readBytes(MacAddress.MAC_ADDRESS_LENGTH)); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | +} |
| ... | @@ -84,7 +84,7 @@ public class SimpleHostStore | ... | @@ -84,7 +84,7 @@ public class SimpleHostStore |
| 84 | descr.hwAddress(), | 84 | descr.hwAddress(), |
| 85 | descr.vlan(), | 85 | descr.vlan(), |
| 86 | descr.location(), | 86 | descr.location(), |
| 87 | - ImmutableSet.of(descr.ipAddress())); | 87 | + ImmutableSet.copyOf(descr.ipAddress())); |
| 88 | synchronized (this) { | 88 | synchronized (this) { |
| 89 | hosts.put(hostId, newhost); | 89 | hosts.put(hostId, newhost); |
| 90 | locations.put(descr.location(), newhost); | 90 | locations.put(descr.location(), newhost); |
| ... | @@ -101,12 +101,12 @@ public class SimpleHostStore | ... | @@ -101,12 +101,12 @@ public class SimpleHostStore |
| 101 | return new HostEvent(HOST_MOVED, host); | 101 | return new HostEvent(HOST_MOVED, host); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | - if (host.ipAddresses().contains(descr.ipAddress())) { | 104 | + if (host.ipAddresses().containsAll(descr.ipAddress())) { |
| 105 | return null; | 105 | return null; |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); | 108 | Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses()); |
| 109 | - addresses.add(descr.ipAddress()); | 109 | + addresses.addAll(descr.ipAddress()); |
| 110 | StoredHost updated = new StoredHost(providerId, host.id(), | 110 | StoredHost updated = new StoredHost(providerId, host.id(), |
| 111 | host.mac(), host.vlan(), | 111 | host.mac(), host.vlan(), |
| 112 | descr.location(), addresses); | 112 | descr.location(), addresses); | ... | ... |
-
Please register or login to post a comment