package io.cattle.platform.servicediscovery.deployment; import static io.cattle.platform.core.model.tables.ServiceIndexTable.*; import io.cattle.platform.activity.ActivityLog; import io.cattle.platform.core.addon.InstanceHealthCheck; import io.cattle.platform.core.addon.InstanceHealthCheck.Strategy; import io.cattle.platform.core.addon.RecreateOnQuorumStrategyConfig; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceIndex; import io.cattle.platform.core.model.Stack; import io.cattle.platform.object.process.StandardProcess; import io.cattle.platform.servicediscovery.api.util.ServiceDiscoveryUtil; import io.cattle.platform.servicediscovery.deployment.impl.DeploymentManagerImpl.DeploymentServiceContext; import io.cattle.platform.servicediscovery.deployment.impl.healthaction.HealthCheckActionHandler; import io.cattle.platform.servicediscovery.deployment.impl.healthaction.NoopHealthCheckActionHandler; import io.cattle.platform.servicediscovery.deployment.impl.healthaction.RecreateHealthCheckActionHandler; import io.cattle.platform.servicediscovery.deployment.impl.healthaction.RecreateOnQuorumHealthCheckActionHandler; import io.cattle.platform.servicediscovery.deployment.impl.unit.DeploymentUnit; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * This class creates new deploymentUnits based on the service requirements (scale/global) * Only healthy units are taken into consideration * Both healthy and unhealthy units are returned (unhealthy units will get cleaned up later after the healthy ones are * deployed) * */ public abstract class ServiceDeploymentPlanner { protected Service service; protected Stack stack; protected List<DeploymentUnit> healthyUnits = new ArrayList<>(); private List<DeploymentUnit> unhealthyUnits = new ArrayList<>(); private List<DeploymentUnit> badUnits = new ArrayList<>(); protected List<DeploymentUnit> incompleteUnits = new ArrayList<>(); protected DeploymentServiceContext context; protected HealthCheckActionHandler healthActionHandler = new RecreateHealthCheckActionHandler(); public ServiceDeploymentPlanner(Service service, List<DeploymentUnit> units, DeploymentServiceContext context, Stack stack) { this.service = service; this.context = context; this.stack = stack; setHealthCheckAction(service, context); populateDeploymentUnits(units); } public String getStatus() { return String.format("Created: %d, Unhealthy: %d, Bad: %d, Incomplete: %d", healthyUnits.size(), unhealthyUnits.size(), badUnits.size(), incompleteUnits.size()); } protected void populateDeploymentUnits(List<DeploymentUnit> units) { List<DeploymentUnit> healthyUnhealthyUnits = new ArrayList<>(); if (units != null) { for (DeploymentUnit unit : units) { if (unit.isError()) { badUnits.add(unit); } else { healthyUnhealthyUnits.add(unit); if (!unit.isComplete()) { incompleteUnits.add(unit); } } } healthActionHandler.populateHealthyUnhealthyUnits(this.healthyUnits, this.unhealthyUnits, healthyUnhealthyUnits); } } protected void setHealthCheckAction(Service service, DeploymentServiceContext context) { Object healthCheckObj = ServiceDiscoveryUtil.getLaunchConfigObject(service, ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME, InstanceConstants.FIELD_HEALTH_CHECK); if (healthCheckObj != null) { InstanceHealthCheck healthCheck = context.jsonMapper.convertValue(healthCheckObj, InstanceHealthCheck.class); if (healthCheck.getStrategy() == Strategy.none) { healthActionHandler = new NoopHealthCheckActionHandler(); } else if (healthCheck.getStrategy() == Strategy.recreateOnQuorum) { if (healthCheck .getRecreateOnQuorumStrategyConfig() == null) { healthCheck.setRecreateOnQuorumStrategyConfig(new RecreateOnQuorumStrategyConfig(1)); } healthActionHandler = new RecreateOnQuorumHealthCheckActionHandler(healthCheck .getRecreateOnQuorumStrategyConfig().getQuorum()); } } } protected Map<String, List<Long>> getUsedServiceIndexesIds(boolean cleanupDuplicates) { // revamp healthy/bad units by excluding units with duplicated indexes Map<String, List<Long>> launchConfigToServiceIndexes = new HashMap<>(); Iterator<DeploymentUnit> it = healthyUnits.iterator(); while (it.hasNext()) { DeploymentUnit healthyUnit = it.next(); for (DeploymentUnitInstance instance : healthyUnit.getDeploymentUnitInstances()) { if (instance.getServiceIndex() == null) { continue; } Long serviceIndexId = instance.getServiceIndex().getId(); String launchConfigName = instance.getLaunchConfigName(); List<Long> usedServiceIndexes = launchConfigToServiceIndexes.get(launchConfigName); if (usedServiceIndexes == null) { usedServiceIndexes = new ArrayList<>(); } if (cleanupDuplicates) { if (usedServiceIndexes.contains(serviceIndexId)) { badUnits.add(healthyUnit); it.remove(); break; } } usedServiceIndexes.add(serviceIndexId); launchConfigToServiceIndexes.put(launchConfigName, usedServiceIndexes); } } return launchConfigToServiceIndexes; } public boolean isHealthcheckInitiailizing() { for (DeploymentUnit unit : this.getAllUnits()) { if (unit.isHealthCheckInitializing()) { return true; } } return false; } public List<DeploymentUnit> deploy(DeploymentUnitInstanceIdGenerator svcInstanceIdGenerator) { List<DeploymentUnit> units = this.deployHealthyUnits(svcInstanceIdGenerator); // sort based on create index Collections.sort(units, new Comparator<DeploymentUnit>() { @Override public int compare(DeploymentUnit d1, DeploymentUnit d2) { return Long.compare(d1.getCreateIndex(), d2.getCreateIndex()); } }); for (DeploymentUnit unit : units) { unit.create(svcInstanceIdGenerator); } for (DeploymentUnit unit : units) { unit.start(); } for (DeploymentUnit unit : units) { unit.waitForStart(); } return units; } protected abstract List<DeploymentUnit> deployHealthyUnits(DeploymentUnitInstanceIdGenerator svcInstanceIdGenerator); public boolean needToReconcileDeployment() { return unhealthyUnits.size() > 0 || badUnits.size() > 0 || incompleteUnits.size() > 0 || needToReconcileDeploymentImpl() || ifHealthyUnitsNeedReconcile(); } private boolean ifHealthyUnitsNeedReconcile() { for (DeploymentUnit unit : healthyUnits) { if (!unit.isStarted()) { return true; } } return false; } protected abstract boolean needToReconcileDeploymentImpl(); public Service getService() { return service; } public void cleanupBadUnits() { List<DeploymentUnit> watchList = new ArrayList<>(); Iterator<DeploymentUnit> it = this.badUnits.iterator(); while (it.hasNext()) { DeploymentUnit next = it.next(); watchList.add(next); next.remove(ServiceConstants.AUDIT_LOG_REMOVE_BAD, ActivityLog.ERROR); it.remove(); } for (DeploymentUnit toWatch : watchList) { toWatch.waitForRemoval(); } } public void cleanupIncompleteUnits() { Iterator<DeploymentUnit> it = this.incompleteUnits.iterator(); while (it.hasNext()) { DeploymentUnit next = it.next(); next.cleanupUnit(); it.remove(); } } public void cleanupUnhealthyUnits() { List<DeploymentUnit> watchList = new ArrayList<>(); Iterator<DeploymentUnit> it = this.unhealthyUnits.iterator(); while (it.hasNext()) { DeploymentUnit next = it.next(); watchList.add(next); next.remove(ServiceConstants.AUDIT_LOG_REMOVE_UNHEATLHY, ActivityLog.INFO); it.remove(); } for (DeploymentUnit toWatch : watchList) { toWatch.waitForRemoval(); } } protected List<DeploymentUnit> getAllUnits() { List<DeploymentUnit> allUnits = new ArrayList<>(); allUnits.addAll(this.healthyUnits); allUnits.addAll(this.unhealthyUnits); allUnits.addAll(this.badUnits); return allUnits; } public void cleanupUnusedAndDuplicatedServiceIndexes() { Map<String, List<Long>> launchConfigToServiceIndexes = getUsedServiceIndexesIds(true); for (ServiceIndex serviceIndex : context.objectManager.find(ServiceIndex.class, SERVICE_INDEX.SERVICE_ID, service.getId(), SERVICE_INDEX.REMOVED, null)) { boolean remove = false; List<Long> usedServiceIndexes = launchConfigToServiceIndexes.get(serviceIndex.getLaunchConfigName()); if (usedServiceIndexes == null) { remove = true; } else { if (!usedServiceIndexes.contains(serviceIndex.getId())) { remove = true; } } if (remove) { context.objectProcessManager.scheduleStandardProcessAsync(StandardProcess.REMOVE, serviceIndex, null); } } } }