package io.cattle.platform.object.monitor.impl; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.async.utils.ResourceTimeoutException; import io.cattle.platform.eventing.annotation.AnnotatedEventListener; import io.cattle.platform.eventing.annotation.EventHandler; import io.cattle.platform.eventing.model.Event; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.resource.ResourceMonitor; import io.cattle.platform.object.resource.ResourcePredicate; import io.cattle.platform.object.util.ObjectUtils; import io.cattle.platform.task.Task; import io.cattle.platform.task.TaskOptions; import io.github.ibuildthecloud.gdapi.id.IdFormatter; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.inject.Inject; import com.netflix.config.DynamicLongProperty; public class ResourceMonitorImpl implements ResourceMonitor, AnnotatedEventListener, Task, TaskOptions { private static final DynamicLongProperty DEFAULT_WAIT = ArchaiusUtil.getLong("resource.monitor.default.wait.millis"); ObjectManager objectManager; ConcurrentMap<String, Object> waiters = new ConcurrentHashMap<String, Object>(); @Inject ObjectMetaDataManager objectMetaDataManger; @Inject IdFormatter idFormatter; Set<String> seen = new HashSet<>(); @EventHandler public void stateChanged(Event event) { resourceChange(event); } @EventHandler public void resourceChange(Event event) { String key = key(event.getResourceType(), event.getResourceId()); Object wait = waiters.get(key); if (wait != null) { synchronized (wait) { wait.notifyAll(); } } } protected String key(Object resourceType, Object resourceId) { resourceId = idFormatter.formatId(resourceType.toString(), resourceId); return String.format("%s:%s", resourceType, resourceId); } @Override public <T> T waitFor(T obj, long timeout, ResourcePredicate<T> predicate) { if (obj == null || predicate == null) { return obj; } String type = objectManager.getType(obj); String kind = ObjectUtils.getKind(obj); if (kind == null) { kind = type; } Object id = ObjectUtils.getId(obj); if (type == null || id == null) { throw new IllegalArgumentException("Type and id are required got [" + type + "] [" + id + "]"); } String printKey = key(kind, id); String key = key(type, id); long end = System.currentTimeMillis() + timeout; Object wait = new Object(); Object oldValue = waiters.putIfAbsent(key, wait); if (oldValue != null) { wait = oldValue; } synchronized (wait) { while (System.currentTimeMillis() < end) { obj = objectManager.reload(obj); if (predicate.evaluate(obj)) { return obj; } synchronized (wait) { try { wait.wait(5000L); } catch (InterruptedException e) { throw new IllegalStateException("Interrupted", e); } } } } throw new ResourceTimeoutException(obj, "Waiting: " + predicate.getMessage() + " [" + printKey + "]"); } @Override public <T> T waitFor(T obj, ResourcePredicate<T> predicate) { return waitFor(obj, DEFAULT_WAIT.get(), predicate); } @Override public void run() { Set<String> previouslySeen = this.seen; this.seen = new HashSet<>(); Map<String, Object> copy = new HashMap<String, Object>(waiters); for (Map.Entry<String, Object> entry : copy.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); seen.add(key); if (previouslySeen.contains(key)) { waiters.remove(entry.getKey(), entry.getValue()); synchronized (value) { value.notifyAll(); } } } } @Override public <T> T waitForState(T obj, final String desiredState) { return waitFor(obj, new ResourcePredicate<T>() { @Override public boolean evaluate(T obj) { return desiredState.equals(ObjectUtils.getState(obj)); } @Override public String getMessage() { return "state to equal " + desiredState; } }); } @Override public <T> T waitForNotTransitioning(T obj) { return waitForNotTransitioning(obj, DEFAULT_WAIT.get()); } @Override public <T> T waitForNotTransitioning(T obj, long wait) { final String type = ObjectUtils.getKind(obj); final String state = ObjectUtils.getState(obj); return waitFor(obj, wait, new ResourcePredicate<T>() { @Override public boolean evaluate(T obj) { return !objectMetaDataManger.isTransitioningState(obj.getClass(), (ObjectUtils.getState(obj))); } @Override public String getMessage() { if (type == null || state == null) { return "a resting state"; } else { return type + " " + state; } } }); } @Override public String getName() { return "resource.change.monitor"; } @Override public boolean isShouldRecord() { return false; } @Override public boolean isShouldLock() { return false; } public ObjectManager getObjectManager() { return objectManager; } @Inject public void setObjectManager(ObjectManager objectManager) { this.objectManager = objectManager; } }