| /* |
| * 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(); |
| } |
| } |