package com.sequenceiq.cloudbreak.core.flow2;
import static com.sequenceiq.cloudbreak.core.flow2.stack.termination.StackTerminationEvent.FORCE_TERMINATION_EVENT;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.cedarsoftware.util.io.JsonReader;
import com.sequenceiq.cloudbreak.cloud.Acceptable;
import com.sequenceiq.cloudbreak.cloud.event.Payload;
import com.sequenceiq.cloudbreak.core.flow2.chain.FlowChainHandler;
import com.sequenceiq.cloudbreak.core.flow2.chain.FlowChains;
import com.sequenceiq.cloudbreak.core.flow2.cluster.downscale.ClusterDownscaleFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.provision.ClusterCreationFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.reset.ClusterResetFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.start.ClusterStartFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.stop.ClusterStopFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.sync.ClusterSyncFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.termination.ClusterTerminationFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.upgrade.ClusterUpgradeFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.upscale.ClusterUpscaleFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.cluster.userpasswd.ClusterCredentialChangeFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.config.FlowConfiguration;
import com.sequenceiq.cloudbreak.core.flow2.stack.downscale.StackDownscaleConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.instance.termination.InstanceTerminationFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.provision.StackCreationFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.repair.ManualStackRepairTriggerFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.start.StackStartFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.stop.StackStopFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.sync.StackSyncFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.termination.StackTerminationEvent;
import com.sequenceiq.cloudbreak.core.flow2.stack.termination.StackTerminationFlowConfig;
import com.sequenceiq.cloudbreak.core.flow2.stack.upscale.StackUpscaleConfig;
import com.sequenceiq.cloudbreak.domain.FlowLog;
import com.sequenceiq.cloudbreak.repository.FlowLogRepository;
import com.sequenceiq.cloudbreak.service.flowlog.FlowLogService;
import reactor.bus.Event;
import reactor.fn.Consumer;
@Component
public class Flow2Handler implements Consumer<Event<? extends Payload>> {
public static final String FLOW_ID = "FLOW_ID";
public static final String FLOW_CHAIN_ID = "FLOW_CHAIN_ID";
public static final String FLOW_FINAL = "FLOWFINAL";
public static final String FLOW_CANCEL = "FLOWCANCEL";
private static final Logger LOGGER = LoggerFactory.getLogger(Flow2Handler.class);
private static final List<String> ALLOWED_PARALLEL_FLOWS = Arrays.asList(
FORCE_TERMINATION_EVENT.event(), StackTerminationEvent.TERMINATION_EVENT.event()
);
private static final List<Class<? extends FlowConfiguration>> RESTARTABLE_FLOWS = Arrays.asList(
StackCreationFlowConfig.class,
StackSyncFlowConfig.class, StackTerminationFlowConfig.class, StackStopFlowConfig.class, StackStartFlowConfig.class,
StackUpscaleConfig.class, StackDownscaleConfig.class,
InstanceTerminationFlowConfig.class,
ManualStackRepairTriggerFlowConfig.class,
ClusterCreationFlowConfig.class,
ClusterSyncFlowConfig.class, ClusterTerminationFlowConfig.class, ClusterCredentialChangeFlowConfig.class,
ClusterStartFlowConfig.class, ClusterStopFlowConfig.class,
ClusterUpscaleFlowConfig.class, ClusterDownscaleFlowConfig.class,
ClusterUpgradeFlowConfig.class, ClusterResetFlowConfig.class
);
@Inject
private FlowLogService flowLogService;
@Resource
private List<FlowConfiguration<?>> flowConfigs;
@Resource
private Map<String, FlowConfiguration<?>> flowConfigurationMap;
@Inject
private FlowChains flowChains;
@Inject
private FlowChainHandler flowChainHandler;
@Inject
private FlowRegister runningFlows;
@Inject
private FlowLogRepository flowLogRepository;
private Lock lock = new ReentrantLock(true);
@Override
public void accept(Event<? extends Payload> event) {
String key = (String) event.getKey();
Payload payload = event.getData();
String flowId = getFlowId(event);
String flowChainId = getFlowChainId(event);
if (FLOW_CANCEL.equals(key)) {
cancelRunningFlows(payload.getStackId());
} else if (FLOW_FINAL.equals(key)) {
finalizeFlow(flowId, flowChainId, payload.getStackId());
} else {
if (flowId == null) {
LOGGER.debug("flow trigger arrived: key: {}, payload: {}", key, payload);
FlowConfiguration<?> flowConfig = flowConfigurationMap.get(key);
if (flowConfig != null && flowConfig.getFlowTriggerCondition().isFlowTriggerable(payload.getStackId())) {
Flow flow;
lock.lock();
try {
if (!acceptFlow(key, payload)) {
LOGGER.info("Flow operation not allowed, other flow is running. Stack ID {}, event {}", payload.getStackId(), key);
return;
}
flowId = UUID.randomUUID().toString();
flow = flowConfig.createFlow(flowId);
flow.initialize();
flowLogService.save(flowId, flowChainId, key, payload, null, flowConfig.getClass(), flow.getCurrentState());
} finally {
lock.unlock();
}
runningFlows.put(flow, flowChainId);
flow.sendEvent(key, payload);
}
} else {
LOGGER.debug("flow control event arrived: key: {}, flowid: {}, payload: {}", key, flowId, payload);
Flow flow = runningFlows.get(flowId);
if (flow != null) {
flowLogService.save(flowId, flowChainId, key, payload, flow.getVariables(), flow.getFlowConfigClass(), flow.getCurrentState());
flow.sendEvent(key, payload);
} else {
LOGGER.info("Cancelled flow finished running. Stack ID {}, flow ID {}, event {}", payload.getStackId(), flowId, key);
}
}
}
}
private boolean acceptFlow(String key, Payload payload) {
if (payload instanceof Acceptable && ((Acceptable) payload).accepted() != null) {
Acceptable acceptable = (Acceptable) payload;
if (!ALLOWED_PARALLEL_FLOWS.contains(key) && isOtherFlowRunning(payload.getStackId())) {
acceptable.accepted().accept(Boolean.FALSE);
return false;
}
if (!acceptable.accepted().isComplete()) {
acceptable.accepted().accept(Boolean.TRUE);
}
}
return true;
}
private boolean isOtherFlowRunning(Long stackId) {
Set<String> flowIds = flowLogRepository.findAllRunningNonTerminationFlowIdsByStackId(stackId);
return !flowIds.isEmpty();
}
private void cancelRunningFlows(Long stackId) {
Set<String> flowIds = flowLogRepository.findAllRunningNonTerminationFlowIdsByStackId(stackId);
LOGGER.debug("flow cancellation arrived: ids: {}", flowIds);
for (String id : flowIds) {
String flowChainId = runningFlows.getFlowChainId(id);
if (flowChainId != null) {
flowChains.removeFullFlowChain(flowChainId);
}
Flow flow = runningFlows.remove(id);
if (flow != null) {
flowLogService.cancel(stackId, id);
}
}
}
private void finalizeFlow(String flowId, String flowChainId, Long stackId) {
LOGGER.debug("flow finalizing arrived: id: {}", flowId);
flowLogService.close(stackId, flowId);
Flow flow = runningFlows.remove(flowId);
if (flowChainId != null) {
if (flow.isFlowFailed()) {
flowChains.removeFullFlowChain(flowChainId);
} else {
flowChains.triggerNextFlow(flowChainId);
}
}
}
public void restartFlow(String flowId) {
FlowLog flowLog = flowLogRepository.findFirstByFlowIdOrderByCreatedDesc(flowId);
if (RESTARTABLE_FLOWS.contains(flowLog.getFlowType())) {
Optional<FlowConfiguration<?>> flowConfig = flowConfigs.stream()
.filter(fc -> fc.getClass().equals(flowLog.getFlowType())).findFirst();
Flow flow = flowConfig.get().createFlow(flowId);
runningFlows.put(flow, flowLog.getFlowChainId());
if (flowLog.getFlowChainId() != null) {
flowChainHandler.restoreFlowChain(flowLog.getFlowChainId());
}
Map<Object, Object> variables = (Map<Object, Object>) JsonReader.jsonToJava(flowLog.getVariables());
flow.initialize(flowLog.getCurrentState(), variables);
Object payload = JsonReader.jsonToJava(flowLog.getPayload());
RestartAction restartAction = flowConfig.get().getRestartAction(flowLog.getNextEvent());
if (restartAction != null) {
restartAction.restart(flowId, flowLog.getFlowChainId(), flowLog.getNextEvent(), payload);
return;
}
}
flowLogService.terminate(flowLog.getStackId(), flowId);
}
private String getFlowId(Event<?> event) {
return event.getHeaders().get(FLOW_ID);
}
private String getFlowChainId(Event<?> event) {
return event.getHeaders().get(FLOW_CHAIN_ID);
}
}