/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.entity.group.zoneaware;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.entity.group.DynamicCluster.ZoneFailureDetector;
import com.google.common.annotations.Beta;
import com.google.common.base.Ticker;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Beta
public abstract class AbstractZoneFailureDetector implements ZoneFailureDetector {
private final ConcurrentMap<Location, ZoneHistory> zoneHistories = Maps.newConcurrentMap();
protected final Ticker ticker;
public AbstractZoneFailureDetector() {
this(Ticker.systemTicker());
}
public AbstractZoneFailureDetector(Ticker ticker) {
this.ticker = ticker;
}
@Override
public void onStartupSuccess(Location loc, Entity entity) {
getZoneHistory(loc).onSuccess(currentTimeMillis());
}
@Override
public void onStartupFailure(Location loc, Entity entity, Throwable cause) {
getZoneHistory(loc).onFailure(currentTimeMillis(), cause);
}
@Override
public boolean hasFailed(Location loc) {
ZoneHistory zoneHistory = getZoneHistory(loc);
return doHasFailed(loc, zoneHistory);
}
protected long currentTimeMillis() {
return TimeUnit.NANOSECONDS.toMillis(ticker.read());
}
protected ZoneHistory getZoneHistory(Location loc) {
ZoneHistory zoneHistory = zoneHistories.get(loc);
if (zoneHistory == null) {
ZoneHistory newZoneHistory = newZoneHistory(loc);
ZoneHistory oldZoneHistory = zoneHistories.putIfAbsent(loc, newZoneHistory);
zoneHistory = (oldZoneHistory != null) ? oldZoneHistory : newZoneHistory;
}
return zoneHistory;
}
protected ZoneHistory newZoneHistory(Location loc) {
return new ZoneHistory();
}
/**
* Warn: called should normally synchronize on zoneHistory while accessing it.
*/
protected abstract boolean doHasFailed(Location loc, ZoneHistory zoneHistory);
/**
* Note: callers please don't side-effect the success/failures/causes fields directly!
* Instead consider sub-classing ZoneHistory, and overriding {@link AbstractZoneFailureDetector#newZoneHistory(Location)}.
*/
public static class ZoneHistory {
public final List<Long> successes = Lists.newLinkedList();
public final List<Long> failures = Lists.newLinkedList();
public final List<Throwable> causes = Lists.newLinkedList();
public synchronized void onSuccess(long date) {
successes.add(date);
}
public synchronized void onFailure(long date, Throwable cause) {
failures.add(date);
causes.add(cause);
}
public synchronized void trimOlderThan(long date) {
assert failures.size() == causes.size() : failures.size()+" failures, but "+causes.size()+" causes; bad synchronization by callers";
for (Iterator<Long> iter = successes.iterator(); iter.hasNext();) {
Long d = iter.next();
if (d < date) iter.remove();
}
for (Iterator<Long> iter = failures.iterator(); iter.hasNext();) {
Iterator<Throwable> causeIter = causes.iterator();
Long d = iter.next();
causeIter.next();
if (d < date) {
iter.remove();
causeIter.remove();
} else {
break;
}
}
}
}
}