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