package com.sequenceiq.cloudbreak.service.usages;
import static com.sequenceiq.cloudbreak.common.type.CloudConstants.BYOS;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import com.google.api.client.util.Lists;
import com.sequenceiq.cloudbreak.api.model.Status;
import com.sequenceiq.cloudbreak.api.model.UsageStatus;
import com.sequenceiq.cloudbreak.domain.CloudbreakUsage;
import com.sequenceiq.cloudbreak.domain.InstanceGroup;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.domain.Template;
import com.sequenceiq.cloudbreak.repository.CloudbreakUsageRepository;
import com.sequenceiq.cloudbreak.repository.StackRepository;
@Service
public class UsageService {
@Inject
private UsageTimeService usageTimeService;
@Inject
private UsagePriceService usagePriceService;
@Inject
private UsageGeneratorService usageGeneratorService;
@Inject
private CloudbreakUsageRepository usageRepository;
@Inject
private StackRepository stackRepository;
public void openUsagesForStack(Stack stack) {
LocalDateTime ldt = LocalDateTime.now();
List<CloudbreakUsage> usages = Lists.newArrayList();
for (InstanceGroup ig : stack.getInstanceGroups()) {
Template template = ig.getTemplate();
String instanceType;
if (stack.getOrchestrator().getType().equals(BYOS)) {
instanceType = "byos-instance";
} else {
instanceType = template == null ? "undefined" : template.getInstanceType();
}
String groupName = ig.getGroupName();
Integer instanceNum = ig.getNodeCount();
usages.add(usageGeneratorService.openNewUsage(stack, instanceType, instanceNum, groupName, ldt));
}
usageRepository.save(usages);
}
public void closeUsagesForStack(Stack stack) {
List<CloudbreakUsage> usages = usageRepository.findOpensForStack(stack.getId());
for (CloudbreakUsage usage : usages) {
closeUsage(usage);
usageRepository.save(usage);
}
}
public void stopUsagesForStack(Stack stack) {
List<CloudbreakUsage> usages = usageRepository.findOpensForStack(stack.getId());
for (CloudbreakUsage usage : usages) {
usage.setStatus(UsageStatus.STOPPED);
Duration newDuration = usageTimeService.calculateNewDuration(usage);
usage.setInstanceHours(usageTimeService.convertToInstanceHours(newDuration));
usage.setDuration(newDuration.toString());
usage.setPeriodStarted(null);
usageRepository.save(usage);
}
}
public void startUsagesForStack(Stack stack) {
List<CloudbreakUsage> usages = usageRepository.findStoppedForStack(stack.getId());
for (CloudbreakUsage usage : usages) {
usage.setStatus(UsageStatus.OPEN);
usage.setPeriodStarted(Date.from(ZonedDateTime.now().toInstant()));
usageRepository.save(usage);
}
}
public void scaleUsagesForStack(Long stackId, String instanceGroupName, int nodeCount) {
CloudbreakUsage usage = usageRepository.getOpenUsageByStackAndGroupName(stackId, instanceGroupName);
if (usage != null) {
Duration newDuration = usageTimeService.calculateNewDuration(usage);
usage.setInstanceHours(usageTimeService.convertToInstanceHours(newDuration));
usage.setDuration(newDuration.toString());
usage.setPeriodStarted(Date.from(ZonedDateTime.now().toInstant()));
if (usage.getPeak() < nodeCount) {
usage.setPeak(nodeCount);
}
usage.setInstanceNum(nodeCount);
usageRepository.save(usage);
}
}
@Scheduled(cron = "0 01 0 * * *")
public void fixUsages() {
reopenOldUsages();
openNewIfNotFound();
}
private void reopenOldUsages() {
List<CloudbreakUsage> usages = usageRepository.findAllOpenAndStopped(Date.from(ZonedDateTime.now().minusDays(1).toInstant()));
List<CloudbreakUsage> newUsages = Lists.newArrayList();
for (CloudbreakUsage usage : usages) {
newUsages.addAll(usageGeneratorService.createClosedUsagesUntilNow(usage));
newUsages.add(closeUsageIfStackKilled(usageGeneratorService.createNewFromUsage(usage)));
closeUsage(usage);
usageRepository.save(usage);
}
usageRepository.save(newUsages);
}
private CloudbreakUsage closeUsageIfStackKilled(CloudbreakUsage usage) {
Stack s = stackRepository.findById(usage.getStackId());
if (s == null || s.getStatus() == Status.DELETE_COMPLETED) {
return closeUsage(usage);
}
return usage;
}
private void openNewIfNotFound() {
List<CloudbreakUsage> usages = usageRepository.findAllOpenAndStopped(Date.from(ZonedDateTime.now().toInstant()));
Set<Long> stackIdsForOpenUsages = usages.stream().map(CloudbreakUsage::getStackId).collect(Collectors.toSet());
List<Stack> stacks = stackRepository.findAllAliveAndProvisioned();
for (Stack stack : stacks) {
if (!stackIdsForOpenUsages.contains(stack.getId())) {
Stack fullStack = stackRepository.findById(stack.getId());
openUsagesForStack(fullStack);
if (fullStack.getStatus() == Status.STOPPED) {
stopUsagesForStack(fullStack);
}
}
}
}
private CloudbreakUsage closeUsage(CloudbreakUsage usage) {
if (usage.getStatus() == UsageStatus.OPEN) {
Duration newDuration = usageTimeService.calculateNewDuration(usage);
usage.setInstanceHours(usageTimeService.convertToInstanceHours(newDuration));
usage.setDuration(newDuration.toString());
usage.setCosts(usagePriceService.calculateCostOfUsage(usage));
}
usage.setStatus(UsageStatus.CLOSED);
return usage;
}
}