Introduced HostMoveTracker to suspend hosts that moves too frequently.
Change-Id: Ied3d939ea7325d6281ff5be75b5799e13e4692cc
diff --git a/core/api/src/main/java/org/onosproject/net/DefaultHost.java b/core/api/src/main/java/org/onosproject/net/DefaultHost.java
index b430020..3d8878d 100644
--- a/core/api/src/main/java/org/onosproject/net/DefaultHost.java
+++ b/core/api/src/main/java/org/onosproject/net/DefaultHost.java
@@ -38,6 +38,7 @@
private final Set<HostLocation> locations;
private final Set<IpAddress> ips;
private final boolean configured;
+ private final boolean suspended;
/**
* Creates an end-station host using the supplied information.
@@ -96,6 +97,32 @@
this.locations = new HashSet<>(locations);
this.ips = new HashSet<>(ips);
this.configured = configured;
+ this.suspended = false;
+ }
+
+ /**
+ * Creates an end-station host using the supplied information.
+ *
+ * @param providerId provider identity
+ * @param id host identifier
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @param locations set of host locations
+ * @param ips host IP addresses
+ * @param configured true if configured via NetworkConfiguration
+ * @param suspended true if the host is suspended due to policy violation.
+ * @param annotations optional key/value annotations
+ */
+ public DefaultHost(ProviderId providerId, HostId id, MacAddress mac,
+ VlanId vlan, Set<HostLocation> locations, Set<IpAddress> ips,
+ boolean configured, boolean suspended, Annotations... annotations) {
+ super(providerId, id, annotations);
+ this.mac = mac;
+ this.vlan = vlan;
+ this.locations = new HashSet<>(locations);
+ this.ips = new HashSet<>(ips);
+ this.configured = configured;
+ this.suspended = suspended;
}
@Override
@@ -140,8 +167,14 @@
}
@Override
+ public boolean suspended() {
+ return this.suspended;
+ }
+
+
+ @Override
public int hashCode() {
- return Objects.hash(id, mac, vlan, locations);
+ return Objects.hash(id, mac, vlan, locations, suspended);
}
@Override
@@ -156,7 +189,8 @@
Objects.equals(this.vlan, other.vlan) &&
Objects.equals(this.locations, other.locations) &&
Objects.equals(this.ipAddresses(), other.ipAddresses()) &&
- Objects.equals(this.annotations(), other.annotations());
+ Objects.equals(this.annotations(), other.annotations()) &&
+ Objects.equals(this.suspended, other.suspended);
}
return false;
}
@@ -171,6 +205,7 @@
.add("ipAddresses", ipAddresses())
.add("annotations", annotations())
.add("configured", configured())
+ .add("suspended", suspended())
.toString();
}
diff --git a/core/api/src/main/java/org/onosproject/net/Host.java b/core/api/src/main/java/org/onosproject/net/Host.java
index b47f843..1293a6e 100644
--- a/core/api/src/main/java/org/onosproject/net/Host.java
+++ b/core/api/src/main/java/org/onosproject/net/Host.java
@@ -72,6 +72,7 @@
/**
* Returns true if configured by NetworkConfiguration.
+ *
* @return configured/learnt dynamically
*/
default boolean configured() {
@@ -79,4 +80,12 @@
}
// TODO: explore capturing list of recent locations to aid in mobility
+ /**
+ * Returns the state of host whether it is in suspended state(offending host due to frequent movement.).
+ *
+ * @return state true if suspended else false.
+ */
+ boolean suspended();
+
+
}
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostEvent.java b/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
index 0ed491f..0fecf2b 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
@@ -48,7 +48,15 @@
/**
* Signifies that a host location has changed.
*/
- HOST_MOVED
+ HOST_MOVED,
+ /**
+ * Signifies that a host is in offending state, eg: frequent host movement.
+ */
+ HOST_SUSPENDED,
+ /**
+ * Signifies that host state in non offending state.
+ */
+ HOST_UNSUSPENDED
}
private Host prevSubject;
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostStore.java b/core/api/src/main/java/org/onosproject/net/host/HostStore.java
index 0b6238b..6d6eb11 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostStore.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostStore.java
@@ -161,4 +161,21 @@
* @param probeMac the source MAC address ONOS uses to probe the host
*/
default void removePendingHostLocation(MacAddress probeMac) {}
+
+ /**
+ * Update the host to suspended state to true
+ * denotes host is in suspended state.
+ *
+ * @param id ID of the host
+ */
+ default void suspend(HostId id){}
+
+ /**
+ * Update the host suspended state to false
+ * denotes host is in unsuspended state.
+ *
+ * @param id ID of the host
+ */
+ default void unsuspend(HostId id){}
+
}
diff --git a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
index b79776d..3e984f7 100644
--- a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
@@ -113,4 +113,6 @@
public Set<Host> getConnectedHosts(DeviceId deviceId) {
return null;
}
+
+
}
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
index 6cb1cb6..feb4c1d 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.net.host.impl;
+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;
@@ -56,14 +57,22 @@
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractProviderService;
import org.osgi.service.component.ComponentContext;
+
import org.slf4j.Logger;
import java.util.Dictionary;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.packet.IPv6.getLinkLocalAddress;
+import static org.onlab.util.Tools.get;
import static org.onosproject.security.AppGuard.checkPermission;
import static org.onosproject.security.AppPermission.Type.HOST_EVENT;
import static org.onosproject.security.AppPermission.Type.HOST_READ;
@@ -123,7 +132,38 @@
label = "Enable/Disable greedy learning of IPv6 link local address")
private boolean greedyLearningIpv6 = false;
+ @Property(name = "hostMoveTrackerEnabled", boolValue = false,
+ label = "Enable tracking of rogue host moves")
+ private boolean hostMoveTrackerEnabled = false;
+
+ private static final int HOST_MOVED_THRESHOLD_IN_MILLIS = 200000;
+ // If the host move happening within given threshold then increment the host move counter
+ private static final int HOST_MOVE_COUNTER = 3;
+ // Max value of the counter after which the host will not be considered as offending host
+ private static final long OFFENDING_HOST_EXPIRY_IN_MINS = 1;
+ // Default pool size of offending host clear executor thread
+ private static final int DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE = 10;
+ private Map<HostId, HostMoveTracker> hostMoveTracker = new ConcurrentHashMap<>();
+
+ @Property(name = "hostMoveThresholdInMillis", intValue = HOST_MOVED_THRESHOLD_IN_MILLIS,
+ label = "Host move threshold in milliseconds")
+ private int hostMoveThresholdInMillis = HOST_MOVED_THRESHOLD_IN_MILLIS;
+
+ @Property(name = "hostMoveCounter", intValue = HOST_MOVE_COUNTER,
+ label = "Host move counter before host is marked as rogue host")
+ private int hostMoveCounter = HOST_MOVE_COUNTER;
+
+ @Property(name = "offendingHostExpiryInMins", longValue = OFFENDING_HOST_EXPIRY_IN_MINS,
+ label = "Expiry time after which the host is cleared from being rogue host")
+ private long offendingHostExpiryInMins = OFFENDING_HOST_EXPIRY_IN_MINS;
+
+ @Property(name = "offendingHostClearThreadPool", intValue = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE,
+ label = "Thread pool capacity")
+ protected int offendingHostClearThreadPool = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE;
+
private HostMonitor monitor;
+ private ScheduledExecutorService offendingHostUnblockExecutor = null;
+
@Activate
@@ -135,8 +175,8 @@
monitor = new HostMonitor(packetService, this, interfaceService, edgePortService);
monitor.setProbeRate(probeRate);
monitor.start();
- modified(context);
cfgService.registerProperties(getClass());
+ modified(context);
log.info("Started");
}
@@ -147,6 +187,9 @@
networkConfigService.removeListener(networkConfigListener);
cfgService.unregisterProperties(getClass(), false);
monitor.shutdown();
+ if (offendingHostUnblockExecutor != null) {
+ offendingHostUnblockExecutor.shutdown();
+ }
log.info("Stopped");
}
@@ -177,15 +220,19 @@
private void readComponentConfiguration(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
Boolean flag;
+ int newHostMoveThresholdInMillis;
+ int newHostMoveCounter;
+ int newOffendinghostPoolSize;
+ long newOffendingHostExpiryInMins;
flag = Tools.isPropertyEnabled(properties, "monitorHosts");
if (flag == null) {
log.info("monitorHosts is not enabled " +
- "using current value of {}", monitorHosts);
+ "using current value of {}", monitorHosts);
} else {
monitorHosts = flag;
log.info("Configured. monitorHosts {}",
- monitorHosts ? "enabled" : "disabled");
+ monitorHosts ? "enabled" : "disabled");
}
Long longValue = Tools.getLongProperty(properties, "probeRate");
@@ -202,19 +249,85 @@
} else {
allowDuplicateIps = flag;
log.info("Removal of duplicate ip address is {}",
- allowDuplicateIps ? "disabled" : "enabled");
+ allowDuplicateIps ? "disabled" : "enabled");
}
flag = Tools.isPropertyEnabled(properties, "greedyLearningIpv6");
if (flag == null) {
log.info("greedy learning is not enabled " +
- "using current value of {}", greedyLearningIpv6);
+ "using current value of {}", greedyLearningIpv6);
} else {
greedyLearningIpv6 = flag;
log.info("Configured. greedyLearningIpv6 {}",
- greedyLearningIpv6 ? "enabled" : "disabled");
+ greedyLearningIpv6 ? "enabled" : "disabled");
}
+ flag = Tools.isPropertyEnabled(properties, "hostMoveTrackerEnabled");
+ if (flag == null) {
+ log.info("Host move tracker is not configured " +
+ "using current value of {}", hostMoveTrackerEnabled);
+ } else {
+ hostMoveTrackerEnabled = flag;
+ log.info("Configured. hostMoveTrackerEnabled {}",
+ hostMoveTrackerEnabled ? "enabled" : "disabled");
+
+ //On enable cfg ,sets default configuration vales added , else use the default values
+ properties = context.getProperties();
+ try {
+ String s = get(properties, "hostMoveThresholdInMillis");
+ newHostMoveThresholdInMillis = isNullOrEmpty(s) ?
+ hostMoveThresholdInMillis : Integer.parseInt(s.trim());
+
+ s = get(properties, "hostMoveCounter");
+ newHostMoveCounter = isNullOrEmpty(s) ? hostMoveCounter : Integer.parseInt(s.trim());
+
+ s = get(properties, "offendingHostExpiryInMins");
+ newOffendingHostExpiryInMins = isNullOrEmpty(s) ?
+ offendingHostExpiryInMins : Integer.parseInt(s.trim());
+
+ s = get(properties, "offendingHostClearThreadPool");
+ newOffendinghostPoolSize = isNullOrEmpty(s) ?
+ offendingHostClearThreadPool : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ newHostMoveThresholdInMillis = HOST_MOVED_THRESHOLD_IN_MILLIS;
+ newHostMoveCounter = HOST_MOVE_COUNTER;
+ newOffendingHostExpiryInMins = OFFENDING_HOST_EXPIRY_IN_MINS;
+ newOffendinghostPoolSize = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE;
+ }
+ if (newHostMoveThresholdInMillis != hostMoveThresholdInMillis) {
+ hostMoveThresholdInMillis = newHostMoveThresholdInMillis;
+ }
+ if (newHostMoveCounter != hostMoveCounter) {
+ hostMoveCounter = newHostMoveCounter;
+ }
+ if (newOffendingHostExpiryInMins != offendingHostExpiryInMins) {
+ offendingHostExpiryInMins = newOffendingHostExpiryInMins;
+ }
+ if (hostMoveTrackerEnabled && offendingHostUnblockExecutor == null) {
+ setupThreadPool();
+ } else if (newOffendinghostPoolSize != offendingHostClearThreadPool
+ && offendingHostUnblockExecutor != null) {
+ offendingHostClearThreadPool = newOffendinghostPoolSize;
+ offendingHostUnblockExecutor.shutdown();
+ offendingHostUnblockExecutor = null;
+ setupThreadPool();
+ } else if (!hostMoveTrackerEnabled && offendingHostUnblockExecutor != null) {
+ offendingHostUnblockExecutor.shutdown();
+ offendingHostUnblockExecutor = null;
+ }
+ if (newOffendinghostPoolSize != offendingHostClearThreadPool) {
+ offendingHostClearThreadPool = newOffendinghostPoolSize;
+ }
+
+ log.debug("modified hostMoveThresholdInMillis: {}, hostMoveCounter: {}, " +
+ "offendingHostExpiryInMins: {} ", hostMoveThresholdInMillis,
+ hostMoveCounter, offendingHostExpiryInMins);
+ }
+
+ }
+
+ private synchronized void setupThreadPool() {
+ offendingHostUnblockExecutor = Executors.newScheduledThreadPool(offendingHostClearThreadPool);
}
/**
@@ -338,8 +451,17 @@
if (!allowDuplicateIps) {
removeDuplicates(hostId, hostDescription);
}
- store.createOrUpdateHost(provider().id(), hostId,
- hostDescription, replaceIps);
+
+ if (!hostMoveTrackerEnabled) {
+ store.createOrUpdateHost(provider().id(), hostId,
+ hostDescription, replaceIps);
+ } else if (!shouldBlock(hostId, hostDescription.locations())) {
+ log.debug("Host move is allowed for host with Id: {} ", hostId);
+ store.createOrUpdateHost(provider().id(), hostId,
+ hostDescription, replaceIps);
+ } else {
+ log.info("Host move is NOT allowed for host with Id: {} , removing from host store ", hostId);
+ }
if (monitorHosts) {
hostDescription.ipAddress().forEach(ip -> {
@@ -391,7 +513,7 @@
allHosts.forEach(eachHost -> {
if (!(eachHost.id().equals(hostId))) {
log.info("Duplicate ip {} found on host {} and {}", ip,
- hostId.toString(), eachHost.id().toString());
+ hostId.toString(), eachHost.id().toString());
store.removeIp(eachHost.id(), ip);
}
});
@@ -476,8 +598,83 @@
Host host = store.getHost(hostId);
return host == null || !host.configured() || host.providerId().equals(provider().id());
}
+
+
+ /**
+ * Deny host move if happening within the threshold time,
+ * track moved host to identify offending hosts.
+ *
+ * @param hostId host identifier
+ * @param locations host locations
+ */
+ private boolean shouldBlock(HostId hostId, Set<HostLocation> locations) {
+ Host host = store.getHost(hostId);
+ // If host is not present in host store means host added for hte first time.
+ if (host != null) {
+ if (host.suspended()) {
+ // Checks host is marked as offending in other onos cluster instance/local instance
+ log.debug("Host id {} is moving frequently hence host moving " +
+ "processing is ignored", hostId);
+ return true;
+ }
+ } else {
+ //host added for the first time.
+ return false;
+ }
+ HostMoveTracker hostMove = hostMoveTracker.computeIfAbsent(hostId, id -> new HostMoveTracker(locations));
+ if (Sets.difference(hostMove.getLocations(), locations).isEmpty() &&
+ Sets.difference(locations, hostMove.getLocations()).isEmpty()) {
+ log.debug("Not hostmove scenario: Host id: {}, Old Host Location: {}, New host Location: {}",
+ hostId, hostMove.getLocations(), locations);
+ return false; // It is not a host move scenario
+ } else if (hostMove.getCounter() >= hostMoveCounter && System.currentTimeMillis() - hostMove.getTimeStamp()
+ < hostMoveThresholdInMillis) {
+ //Check host move is crossed the threshold, then to mark as offending Host
+ log.debug("Host id {} is identified as offending host and entry is added in cache", hostId);
+ hostMove.resetHostMoveTracker(locations);
+ store.suspend(hostId);
+ //Set host suspended flag to false after given offendingHostExpiryInMins
+ offendingHostUnblockExecutor.schedule(new UnblockOffendingHost(hostId),
+ offendingHostExpiryInMins,
+ TimeUnit.MINUTES);
+ return true;
+ } else if (System.currentTimeMillis() - hostMove.getTimeStamp()
+ < hostMoveThresholdInMillis) {
+ //Increment the host move count as hostmove occured within the hostMoveThresholdInMillis time
+ hostMove.updateHostMoveTracker(locations);
+ log.debug("Updated the tracker with the host move registered for host: {}", hostId);
+ } else if (System.currentTimeMillis() - hostMove.getTimeStamp()
+ > hostMoveThresholdInMillis) {
+ //Hostmove is happened after hostMoveThresholdInMillis time so remove from host tracker.
+ hostMove.resetHostMoveTracker(locations);
+ store.unsuspend(hostId);
+ log.debug("Reset the tracker with the host move registered for host: {}", hostId);
+ }
+ return false;
+ }
+
+ // Set host suspended flag to false after given offendingHostExpiryInMins.
+ private final class UnblockOffendingHost implements Runnable {
+ private HostId hostId;
+
+ UnblockOffendingHost(HostId hostId) {
+ this.hostId = hostId;
+ }
+
+ @Override
+ public void run() {
+ // Set the host suspended flag to false
+ try {
+ store.unsuspend(hostId);
+ log.debug("Host {}: Marked host as unsuspended", hostId);
+ } catch (Exception ex) {
+ log.debug("Host {}: not present in host list", hostId);
+ }
+ }
+ }
}
+
// Store delegate to re-post events emitted from the store.
private class InternalStoreDelegate implements HostStoreDelegate {
@Override
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java
new file mode 100644
index 0000000..05a8f77
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.net.host.impl;
+
+import org.onosproject.net.HostLocation;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Used for tracking of the host move.
+ */
+public class HostMoveTracker {
+ private Integer counter;
+ private Long timeStamp;
+ private Set<HostLocation> locations;
+
+ /**
+ * Initialize the instance of HostMoveTracker.
+ *
+ * @param locations List of locations where host is present
+ */
+ public HostMoveTracker(Set<HostLocation> locations) {
+ counter = 1;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Updates locations in HostMoveTracker.
+ *
+ * @param locations List of locations where host is present
+ */
+ public void updateHostMoveTracker(Set<HostLocation> locations) {
+ counter += 1;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Reset hostmove count,timestamp and updated locations.
+ *
+ * @param locations List of locations where host is present
+ */
+ public void resetHostMoveTracker(Set<HostLocation> locations) {
+ counter = 0;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Reset hostmove count and timestamp.
+ */
+ public void resetHostMoveTracker() {
+ counter = 0;
+ timeStamp = System.currentTimeMillis();
+ }
+
+ public Long getTimeStamp() {
+ return timeStamp;
+ }
+
+ public Integer getCounter() {
+ return counter;
+ }
+
+
+ public Set<HostLocation> getLocations() {
+ return locations;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ HostMoveTracker that = (HostMoveTracker) o;
+ return Objects.equals(locations, that.locations) &&
+ Objects.equals(counter, that.counter);
+ }
+
+ @Override
+ public int hashCode() {
+
+ return Objects.hash(locations, counter);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("counter", getCounter())
+ .add("timeStamp", getTimeStamp())
+ .add("locations", getLocations())
+ .toString();
+ }
+}
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
index 01a0b79..7945d64 100644
--- 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
@@ -357,6 +357,48 @@
return ImmutableSet.copyOf(filtered);
}
+ @Override
+ public void suspend(HostId hostId) {
+ hosts.compute(hostId, (id, existingHost) -> {
+ if (existingHost != null) {
+ if (!existingHost.suspended()) {
+ return new DefaultHost(existingHost.providerId(),
+ hostId,
+ existingHost.mac(),
+ existingHost.vlan(),
+ existingHost.locations(),
+ existingHost.ipAddresses(),
+ existingHost.configured(),
+ true,
+ existingHost.annotations());
+ }
+
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public void unsuspend(HostId hostId) {
+ hosts.compute(hostId, (id, existingHost) -> {
+ if (existingHost != null) {
+ if (existingHost.suspended()) {
+ return new DefaultHost(existingHost.providerId(),
+ hostId,
+ existingHost.mac(),
+ existingHost.vlan(),
+ existingHost.locations(),
+ existingHost.ipAddresses(),
+ existingHost.configured(),
+ false,
+ existingHost.annotations());
+
+ }
+ }
+ return null;
+ });
+ }
+
private Set<Host> filter(Collection<DefaultHost> collection, Predicate<DefaultHost> predicate) {
return collection.stream().filter(predicate).collect(Collectors.toSet());
}
@@ -414,7 +456,11 @@
break;
case UPDATE:
updateHostsByIp(host, prevHost);
- if (!Objects.equals(prevHost.locations(), host.locations())) {
+ if (host.suspended() && !prevHost.suspended()) {
+ notifyDelegate(new HostEvent(HOST_SUSPENDED, host, prevHost));
+ } else if (!host.suspended() && prevHost.suspended()) {
+ notifyDelegate(new HostEvent(HOST_UNSUSPENDED, host, prevHost));
+ } else if (!Objects.equals(prevHost.locations(), host.locations())) {
notifyDelegate(new HostEvent(HOST_MOVED, host, prevHost));
} else if (!Objects.equals(prevHost, host)) {
notifyDelegate(new HostEvent(HOST_UPDATED, host, prevHost));