package io.cattle.platform.servicediscovery.deployment.impl.unit; import static io.cattle.platform.core.model.tables.InstanceHostMapTable.*; import io.cattle.platform.activity.ActivityLog; import io.cattle.platform.async.utils.TimeoutException; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.HealthcheckConstants; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.InstanceHostMap; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceExposeMap; import io.cattle.platform.core.model.ServiceIndex; import io.cattle.platform.core.model.Stack; import io.cattle.platform.core.util.SystemLabels; import io.cattle.platform.docker.constants.DockerInstanceConstants; import io.cattle.platform.engine.process.impl.ProcessCancelException; import io.cattle.platform.iaas.api.auditing.AuditEventType; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.object.process.StandardProcess; import io.cattle.platform.object.resource.ResourcePredicate; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.object.util.TransitioningUtils; import io.cattle.platform.process.common.util.ProcessUtils; import io.cattle.platform.servicediscovery.api.resource.ServiceDiscoveryConfigItem; import io.cattle.platform.servicediscovery.api.util.ServiceDiscoveryDnsUtil; import io.cattle.platform.servicediscovery.api.util.ServiceDiscoveryUtil; import io.cattle.platform.servicediscovery.deployment.DeploymentUnitInstance; import io.cattle.platform.servicediscovery.deployment.InstanceUnit; import io.cattle.platform.servicediscovery.deployment.impl.DeploymentManagerImpl.DeploymentServiceContext; import io.cattle.platform.util.exception.InstanceException; import io.cattle.platform.util.exception.ServiceInstanceAllocateException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; public class DefaultDeploymentUnitInstance extends DeploymentUnitInstance implements InstanceUnit { private static final Set<String> ERROR_STATES = new HashSet<String>(Arrays.asList( InstanceConstants.STATE_ERRORING, InstanceConstants.STATE_ERROR)); private static final Set<String> BAD_ALLOCATING_STATES = new HashSet<String>(Arrays.asList( InstanceConstants.STATE_ERRORING, InstanceConstants.STATE_ERROR, InstanceConstants.STATE_STOPPING, InstanceConstants.STATE_STOPPED)); protected String instanceName; protected boolean startOnce; protected Instance instance; protected ServiceIndex serviceIndex; public DefaultDeploymentUnitInstance(DeploymentServiceContext context, String uuid, Service service, String instanceName, Instance instance, String launchConfigName) { super(context, uuid, service, launchConfigName); this.instanceName = instanceName; this.instance = instance; if (this.instance != null) { exposeMap = context.exposeMapDao.findInstanceExposeMap(this.instance); Long svcIndexId = instance.getServiceIndexId(); if (svcIndexId != null) { serviceIndex = context.objectManager .loadResource(ServiceIndex.class, svcIndexId); } } else { this.serviceIndex = createServiceIndex(); } setStartOnce(service, launchConfigName); } @SuppressWarnings("unchecked") public void setStartOnce(Service service, String launghConfig) { Object serviceLabels = ServiceDiscoveryUtil.getLaunchConfigObject(service, launchConfigName, InstanceConstants.FIELD_LABELS); if (serviceLabels != null) { String startOnceLabel = ((Map<String, String>) serviceLabels) .get(SystemLabels.LABEL_SERVICE_CONTAINER_START_ONCE); if (StringUtils.equalsIgnoreCase(startOnceLabel, "true")) { startOnce = true; } } } @Override public boolean isError() { return this.instance != null && (ERROR_STATES.contains(this.instance.getState()) || this.instance.getRemoved() != null); } @Override protected void removeUnitInstance() { removeInstance(instance, context.objectProcessManager); } public static void removeInstance(Instance instance, ObjectProcessManager objectProcessManager) { HashMap<String, Object> data = new HashMap<String, Object>(); data.put(ServiceConstants.PROCESS_DATA_SERVICE_RECONCILE, true); if (!(instance.getState().equals(CommonStatesConstants.REMOVED) || instance.getState().equals( CommonStatesConstants.REMOVING))) { try { objectProcessManager.scheduleStandardProcessAsync(StandardProcess.REMOVE, instance, data); } catch (ProcessCancelException e) { objectProcessManager.scheduleProcessInstanceAsync(InstanceConstants.PROCESS_STOP, instance, ProcessUtils.chainInData(data, InstanceConstants.PROCESS_STOP, InstanceConstants.PROCESS_REMOVE)); } } } @Override @SuppressWarnings("unchecked") public DeploymentUnitInstance create(Map<String, Object> deployParams) { if (createNew()) { Map<String, Object> launchConfigData = populateLaunchConfigData(deployParams); // remove named volumes from the list if (launchConfigData.get(InstanceConstants.FIELD_DATA_VOLUMES) != null) { List<String> dataVolumes = new ArrayList<>(); dataVolumes.addAll((List<String>) launchConfigData.get(InstanceConstants.FIELD_DATA_VOLUMES)); if (deployParams.get(ServiceConstants.FIELD_INTERNAL_VOLUMES) != null) { for (String namedVolume : (List<String>) deployParams .get(ServiceConstants.FIELD_INTERNAL_VOLUMES)) { dataVolumes.remove(namedVolume); } launchConfigData.put(InstanceConstants.FIELD_DATA_VOLUMES, dataVolumes); } launchConfigData.remove(ServiceConstants.FIELD_INTERNAL_VOLUMES); } Pair<Instance, ServiceExposeMap> instanceMapPair = context.exposeMapDao.createServiceInstance(launchConfigData, service); this.instance = instanceMapPair.getLeft(); this.exposeMap = instanceMapPair.getRight(); this.generateAuditLog(AuditEventType.create, ServiceConstants.AUDIT_LOG_CREATE_EXTRA, ActivityLog.INFO); } this.instance = context.objectManager.reload(this.instance); return this; } @SuppressWarnings("unchecked") protected Map<String, Object> populateLaunchConfigData(Map<String, Object> deployParams) { Map<String, Object> launchConfigData = ServiceDiscoveryUtil.buildServiceInstanceLaunchData(service, deployParams, launchConfigName, context.allocationHelper); launchConfigData.put("name", this.instanceName); launchConfigData.remove(ServiceDiscoveryConfigItem.RESTART.getCattleName()); Object labels = launchConfigData.get(InstanceConstants.FIELD_LABELS); if (labels != null) { String overrideHostName = ((Map<String, String>) labels) .get(ServiceConstants.LABEL_OVERRIDE_HOSTNAME); if (StringUtils.equalsIgnoreCase(overrideHostName, "container_name")) { String domainName = (String) launchConfigData.get(DockerInstanceConstants.FIELD_DOMAIN_NAME); String overrideName = getOverrideHostName(domainName, this.instanceName); launchConfigData.put(InstanceConstants.FIELD_HOSTNAME, overrideName); } } launchConfigData.put(InstanceConstants.FIELD_SERVICE_INSTANCE_SERVICE_INDEX_ID, this.serviceIndex.getId()); launchConfigData.put(InstanceConstants.FIELD_SERVICE_INSTANCE_SERVICE_INDEX, this.serviceIndex.getServiceIndex()); launchConfigData.put(InstanceConstants.FIELD_ALLOCATED_IP_ADDRESS, serviceIndex.getAddress()); return launchConfigData; } private String getOverrideHostName(String domainName, String instanceName) { String overrideName = instanceName; if (instanceName != null && instanceName.length() > 64) { // legacy code - to support old data where service suffix object wasn't created String serviceSuffix = ServiceDiscoveryUtil.getServiceSuffixFromInstanceName(instanceName); String serviceSuffixWDivider = instanceName.substring(instanceName.lastIndexOf(serviceSuffix) - 1); int truncateIndex = 64 - serviceSuffixWDivider.length(); if (domainName != null) { truncateIndex = truncateIndex - domainName.length() - 1; } overrideName = instanceName.substring(0, truncateIndex) + serviceSuffixWDivider; } return overrideName; } @Override public boolean createNew() { return this.instance == null; } @Override public DeploymentUnitInstance waitForStartImpl() { this.waitForAllocate(); instance = context.resourceMonitor.waitForNotTransitioning(instance); if (!((startOnce && isStartedOnce()) || (InstanceConstants.STATE_RUNNING.equals(instance.getState())))) { String error = TransitioningUtils.getTransitioningError(instance); String message = String.format("Expected state running but got %s", instance.getState()); if (org.apache.commons.lang3.StringUtils.isNotBlank(error)) { message = message + ": " + error; } throw new InstanceException(message, instance); } return this; } @Override protected boolean isStartedImpl() { if (startOnce) { return isStartedOnce(); } return context.objectManager.reload(this.instance).getState().equalsIgnoreCase(InstanceConstants.STATE_RUNNING); } protected boolean isStartedOnce() { List<String> validStates = Arrays.asList(InstanceConstants.STATE_STOPPED, InstanceConstants.STATE_STOPPING, InstanceConstants.STATE_RUNNING); return validStates.contains(context.objectManager.reload(this.instance).getState()) && context.objectManager.find(InstanceHostMap.class, INSTANCE_HOST_MAP.INSTANCE_ID, instance.getId()).size() > 0; } @Override public Instance getInstance() { return instance; } @Override public boolean isUnhealthy() { if (instance != null) { if (instance.getHealthState() == null) { return false; } boolean unhealthyState = instance.getHealthState().equalsIgnoreCase( HealthcheckConstants.HEALTH_STATE_UNHEALTHY) || instance.getHealthState().equalsIgnoreCase( HealthcheckConstants.HEALTH_STATE_UPDATING_UNHEALTHY); return unhealthyState; } return false; } @Override public void stop() { if (instance != null && instance.getState().equals(InstanceConstants.STATE_RUNNING)) { context.objectProcessManager.scheduleProcessInstanceAsync(InstanceConstants.PROCESS_STOP, instance, null); } } @Override public boolean isHealthCheckInitializing() { return instance != null && instance.getHealthState() != null && HealthcheckConstants.isInit(instance.getHealthState()); } @Override public void waitForAllocate() { try { if (this.instance != null) { if (context.objectManager.find(InstanceHostMap.class, INSTANCE_HOST_MAP.INSTANCE_ID, instance.getId()).size() > 0) { return; } instance = context.resourceMonitor.waitFor(instance, new ResourcePredicate<Instance>() { @Override public boolean evaluate(Instance obj) { if ((startOnce && ERROR_STATES.contains(obj.getState())) || obj.getRemoved() != null || (!startOnce && BAD_ALLOCATING_STATES.contains(obj.getState()))) { String error = TransitioningUtils.getTransitioningError(obj); String message = "Bad instance [" + key(instance) + "] in state [" + obj.getState() + "]"; if (StringUtils.isNotBlank(error)) { message = message + ": " + error; } throw new RuntimeException(message); } return context.objectManager.find(InstanceHostMap.class, INSTANCE_HOST_MAP.INSTANCE_ID, instance.getId()).size() > 0; } @Override public String getMessage() { return "allocated"; } }); } } catch (TimeoutException e) { throw e; } catch (Exception ex) { throw new ServiceInstanceAllocateException("Failed to allocate instance [" + key(instance) + "]", ex, this.instance); } } protected String key(Instance instance) { Object resourceId = context.idFormatter.formatId(instance.getKind(), instance.getId()); return String.format("%s:%s", instance.getKind(), resourceId); } @Override public DeploymentUnitInstance startImpl() { if (instance != null && InstanceConstants.STATE_STOPPED.equals(instance.getState())) { context.activityService.instance(instance, "start", "Starting stopped instance", ActivityLog.INFO); context.objectProcessManager.scheduleProcessInstanceAsync( InstanceConstants.PROCESS_START, instance, null); } return this; } @Override public ServiceIndex getServiceIndex() { return this.serviceIndex; } @SuppressWarnings("unchecked") protected ServiceIndex createServiceIndex() { // create index Stack stack = context.objectManager.loadResource(Stack.class, service.getStackId()); String serviceIndex = ServiceDiscoveryUtil.getGeneratedServiceIndex(stack, service, launchConfigName, instanceName); ServiceIndex serviceIndexObj = context.serviceDao.createServiceIndex(service, launchConfigName, serviceIndex); // allocate ip address if not set if (DataAccessor.fieldBool(service, ServiceConstants.FIELD_SERVICE_RETAIN_IP)) { Object requestedIpObj = ServiceDiscoveryUtil.getLaunchConfigObject(service, launchConfigName, InstanceConstants.FIELD_REQUESTED_IP_ADDRESS); String requestedIp = null; if (requestedIpObj != null) { requestedIp = requestedIpObj.toString(); } else { // can be passed via labels Object labels = ServiceDiscoveryUtil.getLaunchConfigObject(service, launchConfigName, InstanceConstants.FIELD_LABELS); if (labels != null) { requestedIp = ((Map<String, String>) labels).get(SystemLabels.LABEL_REQUESTED_IP); } } context.sdService.allocateIpToServiceIndex(service, serviceIndexObj, requestedIp); } return serviceIndexObj; } @Override public List<String> getSearchDomains() { String stackNamespace = ServiceDiscoveryDnsUtil.getStackNamespace(this.stack, this.service); String serviceNamespace = ServiceDiscoveryDnsUtil .getServiceNamespace(this.stack, this.service); return Arrays.asList(stackNamespace, serviceNamespace); } @Override public Long getCreateIndex() { Long createIndex = this.instance == null ? null : this.instance.getCreateIndex(); return createIndex; } @Override public void scheduleCreate() { if (instance.getState().equalsIgnoreCase(CommonStatesConstants.REQUESTED)) { context.objectProcessManager.scheduleStandardProcessAsync(StandardProcess.CREATE, instance, null); } if (exposeMap.getState().equalsIgnoreCase(CommonStatesConstants.REQUESTED)) { context.objectProcessManager.scheduleStandardProcessAsync(StandardProcess.CREATE, exposeMap, null); } } @Override public boolean isTransitioning() { if (this.instance == null) { return false; } return context.objectMetaDataManager.isTransitioningState(Instance.class, this.instance.getState()); } }