package io.cattle.platform.docker.service.impl; import static io.cattle.platform.core.model.tables.StackTable.*; import static io.cattle.platform.core.model.tables.ServiceTable.*; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.InstanceConstants; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.dao.GenericResourceDao; import io.cattle.platform.core.model.Stack; import io.cattle.platform.core.model.Instance; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.ServiceExposeMap; import io.cattle.platform.docker.process.dao.ComposeDao; import io.cattle.platform.docker.process.lock.ComposeProjectLock; import io.cattle.platform.docker.process.lock.ComposeServiceLock; import io.cattle.platform.docker.process.util.DockerConstants; import io.cattle.platform.docker.service.ComposeManager; import io.cattle.platform.json.JsonMapper; import io.cattle.platform.lock.LockCallback; import io.cattle.platform.lock.LockCallbackNoReturn; import io.cattle.platform.lock.LockManager; import io.cattle.platform.lock.definition.DefaultMultiLockDefinition; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.object.process.StandardProcess; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.servicediscovery.api.dao.ServiceExposeMapDao; import io.cattle.platform.servicediscovery.deployment.impl.unit.DefaultDeploymentUnitInstance; import io.github.ibuildthecloud.gdapi.condition.Condition; import io.github.ibuildthecloud.gdapi.condition.ConditionType; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; public class ComposeManagerImpl implements ComposeManager { public static final String SERVICE_LABEL = "com.docker.compose.service"; public static final String PROJECT_LABEL = "com.docker.compose.project"; @Inject ComposeDao composeDao; @Inject GenericResourceDao resourceDao; @Inject LockManager lockManager; @Inject ObjectManager objectManager; @Inject ServiceExposeMapDao serviceExportMapDao; @Inject ObjectProcessManager objectProcessManager; @Inject JsonMapper jsonMapper; protected String getString(Map<String, Object> labels, String key) { Object value = labels.get(key); return value == null ? null : value.toString(); } @Override public void setupServiceAndInstance(Instance instance) { Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS); String project = getString(labels, PROJECT_LABEL); String service = getString(labels, SERVICE_LABEL); if (StringUtils.isBlank(project) || StringUtils.isBlank(service)) { return; } instance = setupLabels(instance, service, project); getService(instance, service, project); } private Instance setupLabels(Instance instance, String service, String project) { Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS); setIfNot(labels, ServiceConstants.LABEL_SERVICE_DEPLOYMENT_UNIT, io.cattle.platform.util.resource.UUID.randomUUID()); setIfNot(labels, ServiceConstants.LABEL_SERVICE_LAUNCH_CONFIG, ServiceConstants.PRIMARY_LAUNCH_CONFIG_NAME); setIfNot(labels, ServiceConstants.LABEL_STACK_NAME, project); setIfNot(labels, ServiceConstants.LABEL_STACK_SERVICE_NAME, String.format("%s/%s", project, service)); DataAccessor.setField(instance, InstanceConstants.FIELD_LABELS, labels); return objectManager.persist(instance); } protected void setIfNot(Map<String, Object> labels, String key, Object value) { if (!labels.containsKey(key)) { labels.put(key, value); } } protected Service createService(Instance instance) { Map<String, Object> labels = DataAccessor.fieldMap(instance, InstanceConstants.FIELD_LABELS); String project = getString(labels, PROJECT_LABEL); String service = getString(labels, SERVICE_LABEL); Map<String, Object> instanceData = jsonMapper.writeValueAsMap(instance); instanceData.remove(ObjectMetaDataManager.ID_FIELD); instanceData.remove(ObjectMetaDataManager.STATE_FIELD); instanceData.remove("token"); instanceData.remove(ObjectMetaDataManager.DATA_FIELD); instanceData.remove(ObjectMetaDataManager.CREATED_FIELD); Iterator<Entry<String, Object>> iter = instanceData.entrySet().iterator(); while (iter.hasNext()) { Entry<String, Object> entry = iter.next(); if (entry.getValue() == null || entry.getValue() instanceof Number) { iter.remove(); } } Stack stack = getStack(instance.getAccountId(), project); return resourceDao.createAndSchedule(Service.class, SERVICE.NAME, service, SERVICE.ACCOUNT_ID, instance.getAccountId(), SERVICE.STACK_ID, stack.getId(), SERVICE.SELECTOR_CONTAINER, String.format("%s=%s, %s=%s", PROJECT_LABEL, project, SERVICE_LABEL, service), ServiceConstants.FIELD_START_ON_CREATE, true, ServiceConstants.FIELD_LAUNCH_CONFIG, instanceData, SERVICE.KIND, "composeService"); } protected Service getService(final Instance instance, final String name, final String projectName) { Service service = composeDao.getComposeServiceByName(instance.getAccountId(), name, projectName); if (service != null) { return service; } return lockManager.lock(new ComposeServiceLock(instance.getAccountId(), name), new LockCallback<Service>() { @Override public Service doWithLock() { Service service = composeDao.getComposeServiceByName(instance.getAccountId(), name, projectName); if (service != null) { return service; } return createService(instance); } }); } protected Stack getStack(final long accountId, final String project) { Stack env = composeDao.getComposeProjectByName(accountId, project); if (env != null) { return env; } return lockManager.lock(new ComposeProjectLock(accountId, project), new LockCallback<Stack>() { @Override public Stack doWithLock() { Stack env = composeDao.getComposeProjectByName(accountId, project); if (env != null) { return env; } return resourceDao.createAndSchedule(Stack.class, STACK.NAME, project, STACK.ACCOUNT_ID, accountId, STACK.KIND, "composeProject"); } }); } @Override public void cleanupResources(final Service service) { if (!DockerConstants.TYPE_COMPOSE_SERVICE.equals(service.getKind())) { return; } final Stack env = objectManager.loadResource(Stack.class, service.getStackId()); lockManager.lock(new DefaultMultiLockDefinition(new ComposeProjectLock(env.getAccountId(), env.getName()), new ComposeServiceLock(env.getAccountId(), service.getName())), new LockCallbackNoReturn() { @Override public void doWithLockNoResult() { checkAndDelete(service, env); } }); } protected void checkAndDelete(Service service, Stack env) { service = objectManager.reload(service); env = objectManager.reload(env); boolean found = false; for (ServiceExposeMap map : serviceExportMapDao.getUnmanagedServiceInstanceMapsToRemove(service.getId())) { found = true; if (isRemoved(service.getRemoved(), service.getState())) { Instance instance = objectManager.loadResource(Instance.class, map.getInstanceId()); DefaultDeploymentUnitInstance.removeInstance(instance, objectProcessManager); } } if (!found && !isRemoved(service.getRemoved(), service.getState())) { objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, service, null); } env = objectManager.reload(env); if (isRemoved(env.getRemoved(), env.getState())) { return; } List<Service> services = objectManager.find(Service.class, SERVICE.STACK_ID, env.getId(), ObjectMetaDataManager.STATE_FIELD, new Condition(ConditionType.NE, CommonStatesConstants.REMOVING), ObjectMetaDataManager.REMOVED_FIELD, null); if (services.size() == 0) { objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, env, null); } } protected boolean isRemoved(Date removed, String state) { if (removed != null) { return true; } return CommonStatesConstants.REMOVING.equals(state); } }