package com.sequenceiq.cloudbreak.service.decorator;
import static com.sequenceiq.cloudbreak.common.type.CloudConstants.BYOS;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import com.sequenceiq.cloudbreak.api.model.AdjustmentType;
import com.sequenceiq.cloudbreak.api.model.InstanceGroupType;
import com.sequenceiq.cloudbreak.cloud.model.Orchestrator;
import com.sequenceiq.cloudbreak.cloud.model.Platform;
import com.sequenceiq.cloudbreak.cloud.model.PlatformOrchestrators;
import com.sequenceiq.cloudbreak.controller.BadRequestException;
import com.sequenceiq.cloudbreak.controller.validation.template.TemplateValidator;
import com.sequenceiq.cloudbreak.domain.CbUser;
import com.sequenceiq.cloudbreak.domain.Credential;
import com.sequenceiq.cloudbreak.domain.FailurePolicy;
import com.sequenceiq.cloudbreak.domain.FlexSubscription;
import com.sequenceiq.cloudbreak.domain.InstanceGroup;
import com.sequenceiq.cloudbreak.domain.Network;
import com.sequenceiq.cloudbreak.domain.SecurityGroup;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.domain.Template;
import com.sequenceiq.cloudbreak.service.credential.CredentialService;
import com.sequenceiq.cloudbreak.service.flex.FlexSubscriptionService;
import com.sequenceiq.cloudbreak.service.network.NetworkService;
import com.sequenceiq.cloudbreak.service.securitygroup.SecurityGroupService;
import com.sequenceiq.cloudbreak.service.stack.CloudParameterService;
import com.sequenceiq.cloudbreak.service.stack.connector.adapter.ServiceProviderCredentialAdapter;
import com.sequenceiq.cloudbreak.service.stack.flow.ConsulUtils;
import com.sequenceiq.cloudbreak.service.template.TemplateService;
@Service
public class StackDecorator implements Decorator<Stack> {
private static final Logger LOGGER = LoggerFactory.getLogger(StackDecorator.class);
private static final double ONE_HUNDRED = 100.0;
@Inject
private CredentialService credentialService;
@Inject
private NetworkService networkService;
@Inject
private TemplateService templateService;
@Inject
private SecurityGroupService securityGroupService;
@Inject
private Decorator<Template> templateDecorator;
@Inject
private TemplateValidator templateValidator;
@Inject
private ServiceProviderCredentialAdapter credentialAdapter;
@Inject
@Qualifier("conversionService")
private ConversionService conversionService;
@Inject
private CloudParameterService cloudParameterService;
@Inject
private FlexSubscriptionService flexSubscriptionService;
private enum DecorationData {
CREDENTIAL_ID,
NETWORK_ID,
USER,
FLEX_ID
}
@Override
public Stack decorate(Stack subject, Object... data) {
prepareDomainIfDefined(subject, data);
Object credentialId = data[DecorationData.CREDENTIAL_ID.ordinal()];
if (credentialId != null || subject.getCredential() != null) {
Object networkId = data[DecorationData.NETWORK_ID.ordinal()];
prepareCredential(subject, credentialId);
subject.setCloudPlatform(subject.getCredential().cloudPlatform());
if (subject.getInstanceGroups() == null || (networkId == null && subject.getNetwork() == null
&& !BYOS.equals(subject.getCredential().cloudPlatform()))) {
throw new BadRequestException("Instance groups and network must be specified!");
}
prepareNetwork(subject, networkId);
prepareOrchestratorIfNotExist(subject, subject.getCredential());
if (subject.getFailurePolicy() != null) {
validatFailurePolicy(subject, subject.getFailurePolicy());
}
prepareInstanceGroups(subject, data);
prepareFlexSubscription(subject, data);
validate(subject);
}
return subject;
}
private void prepareCredential(Stack subject, Object credentialId) {
if (credentialId != null) {
Credential credential = credentialService.get((Long) credentialId);
subject.setCredential(credential);
}
}
private void prepareNetwork(Stack subject, Object networkId) {
if (networkId != null) {
subject.setNetwork(networkService.getById((Long) networkId));
if (subject.getOrchestrator() != null && ((subject.getOrchestrator().getApiEndpoint() != null || subject.getOrchestrator().getType() == null)
&& !BYOS.equals(subject.cloudPlatform()))) {
throw new BadRequestException("Orchestrator cannot be configured for the stack!");
}
}
}
private void prepareInstanceGroups(Stack subject, Object... data) {
CbUser user = (CbUser) data[DecorationData.USER.ordinal()];
for (InstanceGroup instanceGroup : subject.getInstanceGroups()) {
if (instanceGroup.getTemplate() != null) {
Template template = instanceGroup.getTemplate();
template.setPublicInAccount(subject.isPublicInAccount());
template.setCloudPlatform(subject.cloudPlatform());
templateValidator.validateTemplateRequest(instanceGroup.getTemplate());
template = templateDecorator.decorate(template);
template = templateService.create(user, template);
instanceGroup.setTemplate(template);
}
if (instanceGroup.getSecurityGroup() != null) {
SecurityGroup securityGroup = instanceGroup.getSecurityGroup();
securityGroup.setPublicInAccount(subject.isPublicInAccount());
securityGroup.setCloudPlatform(subject.cloudPlatform());
securityGroup = securityGroupService.create(user, securityGroup);
instanceGroup.setSecurityGroup(securityGroup);
}
}
}
private void prepareDomainIfDefined(Stack subject, Object... data) {
CbUser user = (CbUser) data[DecorationData.USER.ordinal()];
if (subject.getNetwork() != null) {
Network network = subject.getNetwork();
network.setPublicInAccount(subject.isPublicInAccount());
network.setCloudPlatform(subject.getCredential().cloudPlatform());
network = networkService.create(user, network);
subject.setNetwork(network);
}
if (subject.getCredential() != null) {
Credential credential = subject.getCredential();
credential.setPublicInAccount(subject.isPublicInAccount());
credential.setCloudPlatform(subject.getCredential().cloudPlatform());
credential = credentialAdapter.init(credential);
credential = credentialService.create(user, credential);
subject.setCredential(credential);
}
}
private void prepareOrchestratorIfNotExist(Stack subject, Credential credential) {
if (subject.getOrchestrator() == null) {
PlatformOrchestrators orchestrators = cloudParameterService.getOrchestrators();
Orchestrator orchestrator = orchestrators.getDefaults().get(Platform.platform(credential.cloudPlatform()));
com.sequenceiq.cloudbreak.domain.Orchestrator orchestratorObject = new com.sequenceiq.cloudbreak.domain.Orchestrator();
orchestratorObject.setType(orchestrator.value());
subject.setOrchestrator(orchestratorObject);
}
}
private void validate(Stack stack) {
long instanceGroups = stack.getInstanceGroups().stream().filter(ig -> InstanceGroupType.GATEWAY.equals(ig.getInstanceGroupType())).count();
if (instanceGroups == 0 && !BYOS.equals(stack.cloudPlatform())) {
throw new BadRequestException("Gateway instance group not configured");
}
int minNodeCount = ConsulUtils.ConsulServers.SINGLE_NODE_COUNT_LOW.getMin();
int fullNodeCount = stack.getFullNodeCount();
if (fullNodeCount < minNodeCount) {
throw new BadRequestException(String.format("At least %s nodes are required to launch the stack", minNodeCount));
}
}
private void validatFailurePolicy(Stack stack, FailurePolicy failurePolicy) {
if (failurePolicy.getThreshold() == 0L && !AdjustmentType.BEST_EFFORT.equals(failurePolicy.getAdjustmentType())) {
throw new BadRequestException("The threshold can not be 0");
}
if (AdjustmentType.EXACT.equals(failurePolicy.getAdjustmentType())) {
validateExactCount(stack, failurePolicy);
} else if (AdjustmentType.PERCENTAGE.equals(failurePolicy.getAdjustmentType())) {
validatePercentageCount(failurePolicy);
}
}
private void validatePercentageCount(FailurePolicy failurePolicy) {
if (failurePolicy.getThreshold() < 0L || failurePolicy.getThreshold() > ONE_HUNDRED) {
throw new BadRequestException("The percentage of the threshold has to be between 0 an 100.");
}
}
private void validateExactCount(Stack stack, FailurePolicy failurePolicy) {
if (failurePolicy.getThreshold() > stack.getFullNodeCount()) {
throw new BadRequestException("Threshold can not be higher than the node count of the stack.");
}
}
private void prepareFlexSubscription(Stack subject, Object[] data) {
Long flexId = (Long) data[DecorationData.FLEX_ID.ordinal()];
if (flexId != null) {
FlexSubscription flexSubscription = flexSubscriptionService.findOneById(flexId);
subject.setFlexSubscription(flexSubscription);
}
}
}