package io.cattle.platform.systemstack.listener; import static io.cattle.platform.core.model.tables.ServiceTable.*; import static io.cattle.platform.core.model.tables.StackTable.*; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.async.utils.TimeoutException; import io.cattle.platform.configitem.events.ConfigUpdate; import io.cattle.platform.configitem.model.Client; import io.cattle.platform.configitem.version.ConfigItemStatusManager; import io.cattle.platform.core.addon.CatalogTemplate; import io.cattle.platform.core.constants.AccountConstants; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.ProjectTemplateConstants; import io.cattle.platform.core.constants.ServiceConstants; import io.cattle.platform.core.dao.HostDao; import io.cattle.platform.core.model.Account; import io.cattle.platform.core.model.ExternalHandler; import io.cattle.platform.core.model.ProjectTemplate; import io.cattle.platform.core.model.Service; import io.cattle.platform.core.model.Stack; import io.cattle.platform.db.jooq.dao.impl.AbstractJooqDao; import io.cattle.platform.eventing.EventService; import io.cattle.platform.eventing.annotation.AnnotatedEventListener; import io.cattle.platform.eventing.annotation.EventHandler; import io.cattle.platform.eventing.lock.EventLock; import io.cattle.platform.json.JsonMapper; 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.resource.ResourceMonitor; import io.cattle.platform.object.util.DataAccessor; import io.cattle.platform.systemstack.catalog.CatalogService; import io.cattle.platform.systemstack.process.SystemStackTrigger; import io.github.ibuildthecloud.gdapi.condition.Condition; import io.github.ibuildthecloud.gdapi.condition.ConditionType; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.config.DynamicBooleanProperty; public class SystemStackUpdate extends AbstractJooqDao implements AnnotatedEventListener { private static final DynamicBooleanProperty LAUNCH_COMPOSE_EXECUTOR = ArchaiusUtil.getBoolean("compose.executor.execute"); private static final Logger log = LoggerFactory.getLogger(SystemStackUpdate.class); public static final String VIRTUAL_MACHINE = "virtualMachine"; public static final List<String> ORC_PRIORITY = Arrays.asList( AccountConstants.ORC_KUBERNETES, AccountConstants.ORC_SWARM, AccountConstants.ORC_MESOS, AccountConstants.ORC_WINDOWS ); public static final Set<String> ORCS = new HashSet<>(ORC_PRIORITY); @Inject ConfigItemStatusManager itemManager; @Inject EventService eventService; @Inject ObjectManager objectManager; @Inject HostDao hostDao; @Inject ObjectProcessManager processManager; @Inject JsonMapper jsonMapper; @Inject CatalogService catalogService; @Inject ResourceMonitor resourceMonitor; @EventHandler(lock=EventLock.class) public void globalServiceUpdate(ConfigUpdate update) { if (update.getResourceId() == null) { return; } final Client client = new Client(Account.class, new Long(update.getResourceId())); itemManager.runUpdateForEvent(SystemStackTrigger.STACKS, update, client, new Runnable() { @Override public void run() { try { process(client.getResourceId()); } catch (IOException e) { throw new RuntimeException(e); } } }); } protected void startStacks(Account account) throws IOException { if (account.getRemoved() != null) { return; } List<Long> createdStackIds = DataAccessor.fieldLongList(account, AccountConstants.FIELD_CREATED_STACKS); List<Long> startedStackIds = DataAccessor.fieldLongList(account, AccountConstants.FIELD_STARTED_STACKS); if (!startedStackIds.isEmpty() || createdStackIds.isEmpty()) { return; } if (!hostDao.hasActiveHosts(account.getId())) { return; } startedStackIds = new ArrayList<>(); for (Long stackId : createdStackIds) { Stack stack = objectManager.loadResource(Stack.class, stackId); if (stack == null) { continue; } stack = resourceMonitor.waitForNotTransitioning(stack); if (CommonStatesConstants.ACTIVE.equals(stack.getState())) { for (Service service : objectManager.find(Service.class, SERVICE.STACK_ID, stackId, SERVICE.REMOVED, null)) { processManager.scheduleStandardProcess(StandardProcess.ACTIVATE, service, null); } } startedStackIds.add(stackId); } objectManager.setFields(account, AccountConstants.FIELD_STARTED_STACKS, startedStackIds); } public static String chooseOrchestration(List<String> externalIds) { String orchestration = "cattle"; Set<String> installedOrcs = new HashSet<>(); for (String externalId : externalIds) { String orcType = getStackTypeFromExternalId(externalId); if (ORCS.contains(orcType)) { installedOrcs.add(orcType); } } for (String orc : ORC_PRIORITY) { if (installedOrcs.contains(orc)) { if (AccountConstants.ORC_KUBERNETES.equals(orc)) { orchestration = AccountConstants.ORC_KUBERNETES_DISPLAY; } else { orchestration = orc; } } } return orchestration; } protected void process(long accountId) throws IOException { Account account = objectManager.loadResource(Account.class, accountId); if (account == null || account.getRemoved() != null) { return; } createStacks(account); startStacks(account); List<Stack> stacks = objectManager.find(Stack.class, STACK.ACCOUNT_ID, account.getId(), STACK.REMOVED, null, STACK.EXTERNAL_ID, new Condition(ConditionType.LIKE, "%:infra*%")); boolean virtualMachine = false; List<String> externalIds = new ArrayList<>(); for (Stack stack : stacks) { if (!ServiceConstants.isSystem(stack) || CommonStatesConstants.REMOVING.equals(stack.getState())) { continue; } externalIds.add(stack.getExternalId()); String orcType = getStackTypeFromExternalId(stack.getExternalId()); if (VIRTUAL_MACHINE.equals(orcType)) { virtualMachine = true; } } String orchestration = chooseOrchestration(externalIds); boolean oldVm = DataAccessor.fieldBool(account, AccountConstants.FIELD_VIRTUAL_MACHINE); String oldOrch = DataAccessor.fieldString(account, AccountConstants.FIELD_ORCHESTRATION); if (oldVm != virtualMachine || !ObjectUtils.equals(oldOrch, orchestration)) { objectManager.setFields(account, AccountConstants.FIELD_ORCHESTRATION, orchestration, AccountConstants.FIELD_VIRTUAL_MACHINE, virtualMachine); io.cattle.platform.object.util.ObjectUtils.publishChanged(eventService, account.getId(), account.getId(), AccountConstants.TYPE); } } public static String getStackTypeFromExternalId(String externalId) { externalId = StringUtils.removeStart(externalId, "catalog://"); String[] parts = externalId.split(":"); if (parts.length < 2) { return null; } return StringUtils.removeStart(parts[1], "infra*"); } public List<Long> createStacks(Account account) throws IOException { List<Long> createdStackIds = DataAccessor.fieldLongList(account, AccountConstants.FIELD_CREATED_STACKS); if (!createdStackIds.isEmpty()) { return createdStackIds; } ProjectTemplate projectTemplate = objectManager.loadResource(ProjectTemplate.class, account.getProjectTemplateId()); if (projectTemplate == null) { return Collections.emptyList(); } List<CatalogTemplate> templates = DataAccessor.fieldObjectList(projectTemplate, ProjectTemplateConstants.FIELD_STACKS, CatalogTemplate.class, jsonMapper); Map<String, CatalogTemplate> templatesById = catalogService.resolvedExternalIds(templates); createdStackIds = new ArrayList<>(); boolean executorRunning = false; for (Map.Entry<String, CatalogTemplate> entry : templatesById.entrySet()) { String externalId = entry.getKey(); Stack stack = objectManager.findAny(Stack.class, STACK.ACCOUNT_ID, account.getId(), STACK.EXTERNAL_ID, externalId, STACK.REMOVED, null); if (stack == null) { executorRunning = waitForExecutor(executorRunning); stack = catalogService.deploy(account.getId(), entry.getValue()); } createdStackIds.add(stack.getId()); } objectManager.reload(account); objectManager.setFields(account, AccountConstants.FIELD_CREATED_STACKS, createdStackIds); return createdStackIds; } private synchronized boolean waitForExecutor(boolean executorRunning) { if (!LAUNCH_COMPOSE_EXECUTOR.get()) { return true; } if (executorRunning) { return executorRunning; } for (int i = 0; i < 120; i++) { ExternalHandler handler = objectManager.findAny(ExternalHandler.class, ObjectMetaDataManager.NAME_FIELD, "rancher-compose-executor", ObjectMetaDataManager.STATE_FIELD, CommonStatesConstants.ACTIVE); if (handler != null) { return true; } log.info("Waiting for rancher-compose-executor"); try { Thread.sleep(2000); } catch (InterruptedException e) { throw new IllegalStateException(e); } } throw new TimeoutException("Failed to find rancher-compose-executor"); } }