blob: 2a2020635b5bc11582ca740e1416e29fac4fdb61 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.epam.dlab.billing.gcp.service;
import com.epam.dlab.billing.gcp.dao.BillingDAO;
import com.epam.dlab.billing.gcp.documents.Project;
import com.epam.dlab.billing.gcp.documents.UserInstance;
import com.epam.dlab.billing.gcp.model.BillingData;
import com.epam.dlab.billing.gcp.model.GcpBillingData;
import com.epam.dlab.billing.gcp.repository.BillingRepository;
import com.epam.dlab.billing.gcp.repository.ProjectRepository;
import com.epam.dlab.billing.gcp.repository.UserInstanceRepository;
import com.epam.dlab.billing.gcp.util.BillingUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.epam.dlab.billing.gcp.util.BillingUtils.edgeBillingDataStream;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@Service
@Slf4j
public class BillingServiceImpl implements BillingService {
private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final String USAGE_DATE_FORMAT = "yyyy-MM";
private final BillingDAO billingDAO;
private final ProjectRepository projectRepository;
private final UserInstanceRepository userInstanceRepository;
private final BillingRepository billingRepository;
private final MongoTemplate mongoTemplate;
@Value("${dlab.sbn}")
private String sbn;
@Autowired
public BillingServiceImpl(BillingDAO billingDAO, ProjectRepository projectRepository,
UserInstanceRepository userInstanceRepository, BillingRepository billingRepository,
MongoTemplate mongoTemplate) {
this.billingDAO = billingDAO;
this.projectRepository = projectRepository;
this.userInstanceRepository = userInstanceRepository;
this.billingRepository = billingRepository;
this.mongoTemplate = mongoTemplate;
}
@Override
public void updateBillingData() {
try {
final Stream<BillingData> ssnBillingDataStream = BillingUtils.ssnBillingDataStream(sbn);
final Stream<BillingData> billableUserInstances = userInstanceRepository.findAll()
.stream()
.filter(userInstance -> userInstance.getExploratoryId() != null)
.flatMap(BillingUtils::exploratoryBillingDataStream);
final Stream<BillingData> billableEdges = projectRepository.findAll()
.stream()
.collect(Collectors.toMap(Project::getName, Project::getEndpoints))
.entrySet()
.stream()
.flatMap(e -> projectEdges(e.getKey(), e.getValue()));
final Map<String, BillingData> billableResources = Stream.of(billableUserInstances, billableEdges,
ssnBillingDataStream)
.flatMap(s -> s)
.filter(bd -> bd.getDlabId() != null)
.collect(Collectors.toMap(BillingData::getDlabId, b -> b));
log.info("Billable resources are: {}", billableResources);
final Map<String, List<BillingData>> billingDataMap = billingDAO.getBillingData()
.stream()
.map(bd -> toBillingData(bd, getOrDefault(billableResources, bd.getTag())))
.collect(Collectors.groupingBy(bd -> bd.getUsageDate().substring(0,
USAGE_DATE_FORMAT.length())));
billingDataMap.forEach((usageDate, billingDataList) -> {
log.info("Updating billing information for month {}", usageDate);
billingRepository.deleteByUsageDateRegex("^" + usageDate);
billingRepository.insert(billingDataList);
updateExploratoryCost(billingDataList);
});
log.info("Finished updating billing data");
} catch (Exception e) {
log.error("Can not update billing due to: {}", e.getMessage(), e);
}
}
private Stream<BillingData> projectEdges(String projectName, List<Project.Endpoint> endpoints) {
return endpoints
.stream()
.flatMap(endpoint -> edgeBillingDataStream(projectName, sbn, endpoint.getName()));
}
private BillingData getOrDefault(Map<String, BillingData> billableResources, String tag) {
return billableResources.getOrDefault(tag, BillingData.builder().dlabId(tag).build());
}
private void updateExploratoryCost(List<BillingData> billingDataList) {
billingDataList.stream()
.filter(this::userAndExploratoryNamePresent)
.collect(groupByUserNameExploratoryNameCollector())
.forEach(this::updateUserExploratoryBillingData);
}
private void updateUserExploratoryBillingData(String user,
Map<String, List<BillingData>> billableExploratoriesMap) {
billableExploratoriesMap.forEach((exploratoryName, billingInfoList) ->
updateExploratoryBillingData(user, exploratoryName, billingInfoList)
);
}
private Collector<BillingData, ?, Map<String, Map<String, List<BillingData>>>> groupByUserNameExploratoryNameCollector() {
return Collectors.groupingBy(BillingData::getUser, Collectors.groupingBy(BillingData::getExploratoryName));
}
private boolean userAndExploratoryNamePresent(BillingData bd) {
return Objects.nonNull(bd.getUser()) && Objects.nonNull(bd.getExploratoryName());
}
private void updateExploratoryBillingData(String user, String exploratoryName, List<BillingData> billingInfoList) {
userInstanceRepository.findByUserAndExploratoryName(user, exploratoryName).ifPresent(userInstance ->
mongoTemplate.updateFirst(Query.query(where("user").is(user).and("exploratory_name").is(exploratoryName)),
Update.update("cost", getTotalCost(billingInfoList) + "$").set("billing", billingInfoList),
UserInstance.class));
}
private double getTotalCost(List<BillingData> billingInfoList) {
return new BigDecimal(billingInfoList.stream().mapToDouble(BillingData::getCost).sum())
.setScale(2, BigDecimal.ROUND_HALF_UP)
.doubleValue();
}
private BillingData toBillingData(GcpBillingData bd, BillingData billableResource) {
return BillingData.builder()
.displayName(billableResource.getDisplayName())
.cost(bd.getCost().setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue())
.currency(bd.getCurrency())
.product(bd.getProduct())
.project(billableResource.getProject())
.usageDateTo(bd.getUsageDateTo())
.usageDateFrom(bd.getUsageDateFrom())
.usageDate(bd.getUsageDateFrom().format((DateTimeFormatter.ofPattern(DATE_FORMAT))))
.usageType(bd.getUsageType())
.user(billableResource.getUser())
.exploratoryName(billableResource.getExploratoryName())
.computationalName(billableResource.getComputationalName())
.dlabId(bd.getTag())
.resourceType(billableResource.getResourceType())
.build();
}
}