package com.sequenceiq.cloudbreak.core.flow2.stack.sync;
import static com.sequenceiq.cloudbreak.cloud.model.AvailabilityZone.availabilityZone;
import static com.sequenceiq.cloudbreak.cloud.model.Location.location;
import static com.sequenceiq.cloudbreak.cloud.model.Region.region;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import com.sequenceiq.cloudbreak.api.model.Status;
import com.sequenceiq.cloudbreak.cloud.context.CloudContext;
import com.sequenceiq.cloudbreak.cloud.event.Payload;
import com.sequenceiq.cloudbreak.cloud.event.Selectable;
import com.sequenceiq.cloudbreak.cloud.event.resource.GetInstancesStateRequest;
import com.sequenceiq.cloudbreak.cloud.event.resource.GetInstancesStateResult;
import com.sequenceiq.cloudbreak.cloud.model.CloudCredential;
import com.sequenceiq.cloudbreak.cloud.model.CloudInstance;
import com.sequenceiq.cloudbreak.cloud.model.Location;
import com.sequenceiq.cloudbreak.converter.spi.CredentialToCloudCredentialConverter;
import com.sequenceiq.cloudbreak.converter.spi.InstanceMetaDataToCloudInstanceConverter;
import com.sequenceiq.cloudbreak.core.flow2.AbstractAction;
import com.sequenceiq.cloudbreak.core.flow2.event.StackSyncTriggerEvent;
import com.sequenceiq.cloudbreak.core.flow2.stack.AbstractStackFailureAction;
import com.sequenceiq.cloudbreak.core.flow2.stack.FlowMessageService;
import com.sequenceiq.cloudbreak.core.flow2.stack.Msg;
import com.sequenceiq.cloudbreak.core.flow2.stack.StackFailureContext;
import com.sequenceiq.cloudbreak.domain.InstanceMetaData;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.logger.MDCBuilder;
import com.sequenceiq.cloudbreak.reactor.api.event.StackEvent;
import com.sequenceiq.cloudbreak.reactor.api.event.StackFailureEvent;
import com.sequenceiq.cloudbreak.repository.InstanceMetaDataRepository;
import com.sequenceiq.cloudbreak.service.stack.StackService;
import com.sequenceiq.cloudbreak.service.stack.flow.StackSyncService;
@Configuration
public class StackSyncActions {
private static final Logger LOGGER = LoggerFactory.getLogger(StackSyncActions.class);
@Inject
private InstanceMetaDataToCloudInstanceConverter cloudInstanceConverter;
@Inject
private StackSyncService stackSyncService;
@Inject
private FlowMessageService flowMessageService;
@Bean(name = "SYNC_STATE")
public Action stackSyncAction() {
return new AbstractStackSyncAction<StackSyncTriggerEvent>(StackSyncTriggerEvent.class) {
@Override
protected void prepareExecution(StackSyncTriggerEvent payload, Map<Object, Object> variables) {
variables.put(STATUS_UPDATE_ENABLED, payload.getStatusUpdateEnabled());
}
@Override
protected void doExecute(StackSyncContext context, StackSyncTriggerEvent payload, Map<Object, Object> variables) throws Exception {
sendEvent(context);
}
@Override
protected Selectable createRequest(StackSyncContext context) {
List<CloudInstance> cloudInstances = cloudInstanceConverter.convert(context.getInstanceMetaData());
return new GetInstancesStateRequest<GetInstancesStateResult>(context.getCloudContext(), context.getCloudCredential(), cloudInstances);
}
};
}
@Bean(name = "SYNC_FINISHED_STATE")
public Action stackSyncFinishedAction() {
return new AbstractStackSyncAction<GetInstancesStateResult>(GetInstancesStateResult.class) {
@Override
protected void doExecute(StackSyncContext context, GetInstancesStateResult payload, Map<Object, Object> variables) throws Exception {
stackSyncService.updateInstances(context.getStack(), context.getInstanceMetaData(), payload.getStatuses(), context.isStatusUpdateEnabled());
sendEvent(context);
}
@Override
protected Selectable createRequest(StackSyncContext context) {
return new StackEvent(StackSyncEvent.SYNC_FINALIZED_EVENT.event(), context.getStack().getId());
}
};
}
@Bean(name = "SYNC_FAILED_STATE")
public Action stackSyncFailedAction() {
return new AbstractStackFailureAction<StackSyncState, StackSyncEvent>() {
@Override
protected void doExecute(StackFailureContext context, StackFailureEvent payload, Map<Object, Object> variables) throws Exception {
LOGGER.error("Error during Stack synchronization flow:", payload.getException());
flowMessageService.fireEventAndLog(context.getStack().getId(), Msg.STACK_SYNC_INSTANCE_STATUS_COULDNT_DETERMINE, Status.AVAILABLE.name());
sendEvent(context);
}
@Override
protected Selectable createRequest(StackFailureContext context) {
return new StackEvent(StackSyncEvent.SYNC_FAIL_HANDLED_EVENT.event(), context.getStack().getId());
}
};
}
private abstract static class AbstractStackSyncAction<P extends Payload> extends AbstractAction<StackSyncState, StackSyncEvent, StackSyncContext, P> {
static final String STATUS_UPDATE_ENABLED = "STATUS_UPDATE_ENABLED";
@Inject
private StackService stackService;
@Inject
private InstanceMetaDataRepository instanceMetaDataRepository;
@Inject
private CredentialToCloudCredentialConverter credentialConverter;
@Inject
private InstanceMetaDataToCloudInstanceConverter cloudInstanceConverter;
protected AbstractStackSyncAction(Class<P> payloadClass) {
super(payloadClass);
}
@Override
protected StackSyncContext createFlowContext(String flowId, StateContext<StackSyncState, StackSyncEvent> stateContext, P payload) {
Map<Object, Object> variables = stateContext.getExtendedState().getVariables();
Long stackId = payload.getStackId();
Stack stack = stackService.getById(stackId);
MDCBuilder.buildMdcContext(stack);
//We need a find all in stack where we have hostmetadata associated
List<InstanceMetaData> instances = new ArrayList<>(instanceMetaDataRepository.findAllInStack(stackId));
Location location = location(region(stack.getRegion()), availabilityZone(stack.getAvailabilityZone()));
CloudContext cloudContext = new CloudContext(stack.getId(), stack.getName(), stack.cloudPlatform(), stack.getOwner(), stack.getPlatformVariant(),
location);
CloudCredential cloudCredential = credentialConverter.convert(stack.getCredential());
return new StackSyncContext(flowId, stack, instances, cloudContext, cloudCredential, isStatusUpdateEnabled(variables));
}
@Override
protected Object getFailurePayload(P payload, Optional<StackSyncContext> flowContext, Exception ex) {
return new StackFailureEvent(payload.getStackId(), ex);
}
private Boolean isStatusUpdateEnabled(Map<Object, Object> variables) {
return (Boolean) variables.get(STATUS_UPDATE_ENABLED);
}
}
}