package io.cattle.iaas.healthcheck.service.impl; import static io.cattle.platform.core.constants.HealthcheckConstants.*; import static io.cattle.platform.core.model.tables.HealthcheckInstanceHostMapTable.*; import static io.cattle.platform.core.model.tables.HealthcheckInstanceTable.*; import static io.cattle.platform.core.util.SystemLabels.*; import io.cattle.iaas.healthcheck.service.HealthcheckService; import io.cattle.platform.allocator.dao.AllocatorDao; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.HealthcheckConstants; import io.cattle.platform.core.dao.GenericMapDao; import io.cattle.platform.core.dao.GenericResourceDao; import io.cattle.platform.core.dao.HostDao; import io.cattle.platform.core.dao.NetworkDao; import io.cattle.platform.core.model.HealthcheckInstance; import io.cattle.platform.core.model.HealthcheckInstanceHostMap; import io.cattle.platform.core.model.Host; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.InstanceHostMap; import io.cattle.platform.lock.LockCallbackNoReturn; import io.cattle.platform.lock.LockManager; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.util.type.CollectionUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.inject.Inject; import org.apache.commons.collections.TransformerUtils; public class HealthcheckServiceImpl implements HealthcheckService { @Inject GenericMapDao mapDao; @Inject ObjectManager objectManager; @Inject GenericResourceDao resourceDao; @Inject ObjectProcessManager objectProcessManager; @Inject LockManager lockManager; @Inject AllocatorDao allocatorDao; @Inject HostDao hostDao; @Inject NetworkDao ntwkDao; @Override public void updateHealthcheck(String healthcheckInstanceHostMapUuid, final long externalTimestamp, final String healthState) { HealthcheckInstanceHostMap hcihm = objectManager.findOne(HealthcheckInstanceHostMap.class, ObjectMetaDataManager.UUID_FIELD, healthcheckInstanceHostMapUuid); if (!shouldUpdate(hcihm, externalTimestamp, healthState)) { return; } String hcihmNewState = healthState; if (healthState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_INITIALIZING)) { if (!hcihm.getState().equalsIgnoreCase(healthState)) { hcihmNewState = HealthcheckConstants.HEALTH_STATE_REINITIALIZING; } } final HealthcheckInstanceHostMap updatedHcihm = objectManager.setFields(hcihm, HEALTHCHECK_INSTANCE_HOST_MAP.EXTERNAL_TIMESTAMP, externalTimestamp, HEALTHCHECK_INSTANCE_HOST_MAP.HEALTH_STATE, hcihmNewState); lockManager.lock(new HealthcheckInstanceLock(hcihm.getHealthcheckInstanceId()), new LockCallbackNoReturn() { @Override public void doWithLockNoResult() { processHealthcheckInstance(updatedHcihm, healthState); } }); } @Override public void healthCheckReconcile(final HealthcheckInstanceHostMap hcihm, final String healthState) { final HealthcheckInstance hcInstance = objectManager.loadResource(HealthcheckInstance.class, hcihm.getHealthcheckInstanceId()); lockManager.lock(new HealthcheckInstanceLock(hcInstance.getId()), new LockCallbackNoReturn() { @Override public void doWithLockNoResult() { processHealthcheckInstance(hcihm, healthState); } }); } protected void processHealthcheckInstance(HealthcheckInstanceHostMap hcihm, String healthState) { HealthcheckInstance hcInstance = objectManager.loadResource(HealthcheckInstance.class, hcihm.getHealthcheckInstanceId()); String updateWithState = determineNewHealthState(hcInstance, hcihm, healthState); if (updateWithState == null) { return; } Instance instance = objectManager.loadResource(Instance.class, hcInstance.getInstanceId()); updateInstanceHealthState(instance, updateWithState); } protected boolean shouldUpdate(HealthcheckInstanceHostMap hcihm, long externalTimestamp, String healthState) { HealthcheckInstance hcInstance = objectManager.loadResource(HealthcheckInstance.class, hcihm.getHealthcheckInstanceId()); if (healthState.equalsIgnoreCase(HEALTH_STATE_UNHEALTHY) && HealthcheckConstants.isInit(getHealthState(hcInstance))) { return false; } if (hcihm.getExternalTimestamp() == null) { return true; } if (externalTimestamp < hcihm.getExternalTimestamp()) { return false; } return true; } protected String determineNewHealthState(HealthcheckInstance hcInstance, HealthcheckInstanceHostMap hcihm, String healthState) { List<HealthcheckInstanceHostMap> others = objectManager.find(HealthcheckInstanceHostMap.class, HEALTHCHECK_INSTANCE_HOST_MAP.HEALTHCHECK_INSTANCE_ID, hcInstance.getId(), HEALTHCHECK_INSTANCE_HOST_MAP.STATE, CommonStatesConstants.ACTIVE); boolean currentlyHealthy = HEALTH_STATE_HEALTHY.equals(getHealthState(hcInstance)); if (healthState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_HEALTHY)) { return currentlyHealthy ? null : healthState; } else if (healthState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_INITIALIZING)) { return currentlyHealthy ? HealthcheckConstants.HEALTH_STATE_REINITIALIZING : null; } else { if (!currentlyHealthy) { return null; } boolean allUnHealthy = true; int i = 0; for (HealthcheckInstanceHostMap map : others) { if (map.getId().equals(hcihm.getId())) { continue; } i++; if (!HEALTH_STATE_UNHEALTHY.equals(map.getHealthState())) { allUnHealthy = false; break; } } if (healthState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_RECONCILE)) { // if no other hosts are present, don't change the state; // otherwise calculate based on the health check present if (i == 0) { return null; } else { return allUnHealthy ? HealthcheckConstants.HEALTH_STATE_UNHEALTHY : null; } } return allUnHealthy ? healthState : null; } } protected String getHealthState(HealthcheckInstance hcInstance) { Instance instance = objectManager.loadResource(Instance.class, hcInstance.getInstanceId()); return instance == null ? null : instance.getHealthState(); } @Override public void updateInstanceHealthState(Instance instance, String updateWithState) { if (instance != null) { if (updateWithState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_HEALTHY)) { objectProcessManager.scheduleProcessInstance(HealthcheckConstants.PROCESS_UPDATE_HEALTHY, instance, null); } else if (updateWithState.equalsIgnoreCase(HealthcheckConstants.HEALTH_STATE_REINITIALIZING)) { objectProcessManager.scheduleProcessInstance(HealthcheckConstants.PROCESS_UPDATE_REINITIALIZING, instance, null); } else { objectProcessManager.scheduleProcessInstance(HealthcheckConstants.PROCESS_UPDATE_UNHEALTHY, instance, null); } } } @Override public void registerForHealtcheck(final HealthcheckInstanceType instanceType, final long id) { final Long accountId = getAccountId(instanceType, id); if (accountId == null) { return; } lockManager.lock(new HealthcheckRegisterLock(id, instanceType), new LockCallbackNoReturn() { @Override public void doWithLockNoResult() { // 1. create healthcheckInstance mapping HealthcheckInstance healthInstance = createHealtcheckInstance(id, accountId); // 2. create healtcheckInstance to hosts mappings createHealthCheckHostMaps(instanceType, id, accountId, healthInstance); } }); } protected HealthcheckInstance createHealtcheckInstance(long id, Long accountId) { HealthcheckInstance healthInstance = objectManager.findAny(HealthcheckInstance.class, HEALTHCHECK_INSTANCE.ACCOUNT_ID, accountId, HEALTHCHECK_INSTANCE.INSTANCE_ID, id, HEALTHCHECK_INSTANCE.REMOVED, null); if (healthInstance == null) { healthInstance = resourceDao.createAndSchedule(HealthcheckInstance.class, HEALTHCHECK_INSTANCE.ACCOUNT_ID, accountId, HEALTHCHECK_INSTANCE.INSTANCE_ID, id); } return healthInstance; } protected void createHealthCheckHostMaps(HealthcheckInstanceType instanceType, long id, Long accountId, HealthcheckInstance healthInstance) { Long inferiorHostId = getInstanceHostId(instanceType, id); List<Long> healthCheckHostIds = getHealthCheckHostIds(healthInstance, inferiorHostId); for (Long healthCheckHostId : healthCheckHostIds) { HealthcheckInstanceHostMap healthHostMap = mapDao.findNonRemoved(HealthcheckInstanceHostMap.class, Host.class, healthCheckHostId, HealthcheckInstance.class, healthInstance.getId()); Long instanceId = (instanceType == HealthcheckInstanceType.INSTANCE ? id : null); if (healthHostMap == null) { resourceDao.createAndSchedule(HealthcheckInstanceHostMap.class, HEALTHCHECK_INSTANCE_HOST_MAP.HOST_ID, healthCheckHostId, HEALTHCHECK_INSTANCE_HOST_MAP.HEALTHCHECK_INSTANCE_ID, healthInstance.getId(), HEALTHCHECK_INSTANCE_HOST_MAP.ACCOUNT_ID, accountId, HEALTHCHECK_INSTANCE_HOST_MAP.INSTANCE_ID, instanceId); } } } @SuppressWarnings("unchecked") private List<Long> getHealthCheckHostIds(HealthcheckInstance healthInstance, Long inferiorHostId) { int requiredNumber = 3; List<? extends HealthcheckInstanceHostMap> existingHostMaps = mapDao.findNonRemoved( HealthcheckInstanceHostMap.class, HealthcheckInstance.class, healthInstance.getId()); List<? extends Host> availableActiveHosts = allocatorDao.getActiveHosts(healthInstance.getAccountId()); // skip hosts labeled accordingly Iterator<? extends Host> it = availableActiveHosts.iterator(); while (it.hasNext()) { String skip = (String) CollectionUtils.getNestedValue(it.next().getData(), "fields", "labels", LABEL_HEALTHCHECK_SKIP); if (skip != null && "true".equals(skip)) { it.remove(); } } List<Long> availableActiveHostIds = (List<Long>) org.apache.commons.collections.CollectionUtils.collect(availableActiveHosts, TransformerUtils.invokerTransformer("getId")); List<Long> allocatedActiveHostIds = (List<Long>) org.apache.commons.collections.CollectionUtils.collect(existingHostMaps, TransformerUtils.invokerTransformer("getHostId")); // skip the host that if not active (being removed, reconnecting, etc) Iterator<Long> it2 = allocatedActiveHostIds.iterator(); while (it2.hasNext()) { Long allocatedHostId = it2.next(); if (!availableActiveHostIds.contains(allocatedHostId)) { it2.remove(); } } // return if allocated active hosts is >= required number of hosts for healtcheck if (allocatedActiveHostIds.size() >= requiredNumber) { return new ArrayList<>(); } // remove allocated hosts from the available hostIds and shuffle the list to randomize the choice availableActiveHostIds.removeAll(allocatedActiveHostIds); requiredNumber = requiredNumber - allocatedActiveHostIds.size(); Collections.shuffle(availableActiveHostIds); // place inferiorHostId to the end of the list if (inferiorHostId != null) { if (availableActiveHostIds.contains(inferiorHostId)) { availableActiveHostIds.remove(inferiorHostId); if (availableActiveHostIds.isEmpty() && allocatedActiveHostIds.isEmpty()) { availableActiveHostIds.add(inferiorHostId); } } } // Figure out the final number of hosts int returnedNumber = requiredNumber > availableActiveHostIds.size() ? availableActiveHostIds.size() : requiredNumber; return availableActiveHostIds.subList(0, returnedNumber); } private Long getInstanceHostId(HealthcheckInstanceType type, long instanceId) { if (type == HealthcheckInstanceType.INSTANCE) { List<? extends InstanceHostMap> maps = mapDao.findNonRemoved(InstanceHostMap.class, Instance.class, instanceId); if (!maps.isEmpty()) { return maps.get(0).getHostId(); } } return null; } private Long getAccountId(HealthcheckInstanceType type, long instanceId) { if (type == HealthcheckInstanceType.INSTANCE) { Instance instance = objectManager.loadResource(Instance.class, instanceId); if (instance != null) { return instance.getAccountId(); } } return null; } }