com.epam.dlab.backendapi.dao.aws.AwsBillingDAO.java Source code

Java tutorial

Introduction

Here is the source code for com.epam.dlab.backendapi.dao.aws.AwsBillingDAO.java

Source

/*
 * Copyright (c) 2017, EPAM SYSTEMS INC
 *
 * Licensed 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.backendapi.dao.aws;

import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.dao.BillingDAO;
import com.epam.dlab.backendapi.resources.dto.aws.AwsBillingFilter;
import com.epam.dlab.backendapi.roles.RoleType;
import com.epam.dlab.backendapi.roles.UserRoles;
import com.epam.dlab.billing.BillingCalculationUtils;
import com.epam.dlab.billing.DlabResourceType;
import com.epam.dlab.dto.UserInstanceStatus;
import com.epam.dlab.util.UsernameUtils;
import com.google.common.collect.Lists;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.FindIterable;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.epam.dlab.backendapi.dao.MongoCollections.BILLING;
import static com.epam.dlab.backendapi.dao.MongoCollections.USER_EDGE;
import static com.epam.dlab.model.aws.ReportLine.*;
import static com.mongodb.client.model.Accumulators.*;
import static com.mongodb.client.model.Aggregates.*;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.fields;
import static com.mongodb.client.model.Projections.include;

/**
 * DAO for user billing.
 */
public class AwsBillingDAO extends BillingDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(AwsBillingDAO.class);

    public static final String DLAB_RESOURCE_TYPE = "dlab_resource_type";
    public static final String USAGE_DATE_START = "usage_date_start";
    public static final String USAGE_DATE_END = "usage_date_end";
    public static final String TAG_RESOURCE_ID = "tag_resource_id";

    /**
     * Add the conditions to the list.
     *
     * @param conditions the list of conditions.
     * @param fieldName  the name of field.
     * @param values     the values.
     */
    private void addCondition(List<Bson> conditions, String fieldName, List<String> values) {
        if (values != null && !values.isEmpty()) {
            conditions.add(in(fieldName, values));
        }
    }

    /**
     * Build and returns the billing report.
     *
     * @param userInfo user info
     * @param filter   the filter for report data.
     * @return billing report
     */
    public Document getReport(UserInfo userInfo, AwsBillingFilter filter) {
        // Create filter
        List<Bson> conditions = new ArrayList<>();
        boolean isFullReport = UserRoles.checkAccess(userInfo, RoleType.PAGE,
                "/api/infrastructure_provision/billing");
        if (isFullReport) {
            usersToLowerCase(filter.getUser());
        } else {
            filter.setUser(Lists.newArrayList(userInfo.getName().toLowerCase()));
        }
        addCondition(conditions, USER, filter.getUser());
        addCondition(conditions, FIELD_PRODUCT, filter.getProduct());
        addCondition(conditions, DLAB_RESOURCE_TYPE, DlabResourceType.getResourceTypeIds(filter.getResourceType()));

        addAnotherConditionsIfNecessary(conditions, filter);

        // Create aggregation conditions

        List<Bson> pipeline = new ArrayList<>();
        if (!conditions.isEmpty()) {
            LOGGER.trace("Filter conditions is {}", conditions);
            pipeline.add(match(and(conditions)));
        }
        pipeline.add(group(
                getGroupingFields(USER, FIELD_DLAB_ID, DLAB_RESOURCE_TYPE, FIELD_PRODUCT, FIELD_RESOURCE_TYPE,
                        FIELD_CURRENCY_CODE),
                sum(FIELD_COST, "$" + FIELD_COST), min(USAGE_DATE_START, "$" + FIELD_USAGE_DATE),
                max(USAGE_DATE_END, "$" + FIELD_USAGE_DATE)));
        pipeline.add(sort(new Document(ID + "." + USER, 1).append(ID + "." + FIELD_DLAB_ID, 1)
                .append(ID + "." + DLAB_RESOURCE_TYPE, 1).append(ID + "." + FIELD_PRODUCT, 1)));

        // Get billing report and the list of shape info
        AggregateIterable<Document> agg = getCollection(BILLING).aggregate(pipeline);
        Map<String, ShapeInfo> shapes = getShapes(filter.getShape());

        // Build billing report lines
        List<Document> reportItems = new ArrayList<>();
        boolean filterByShape = !(filter.getShape() == null || filter.getShape().isEmpty());
        String usageDateStart = null;
        String usageDateEnd = null;
        double costTotal = 0;

        for (Document d : agg) {
            Document id = (Document) d.get(ID);
            String resourceId = id.getString(FIELD_DLAB_ID);
            ShapeInfo shape = shapes.get(resourceId);
            final UserInstanceStatus status = Optional.ofNullable(shape).map(ShapeInfo::getStatus).orElse(null);
            if ((filterByShape && shape == null) || (!filter.getStatuses().isEmpty()
                    && filter.getStatuses().stream().noneMatch(s -> s.equals(status)))) {
                continue;
            }

            String resourceTypeId = DlabResourceType.getResourceTypeName(id.getString(DLAB_RESOURCE_TYPE));
            String shapeName = generateShapeName(shape);
            String dateStart = d.getString(USAGE_DATE_START);
            if (StringUtils.compare(usageDateStart, dateStart, false) > 0) {
                usageDateStart = dateStart;
            }
            String dateEnd = d.getString(USAGE_DATE_END);
            if (StringUtils.compare(usageDateEnd, dateEnd) < 0) {
                usageDateEnd = dateEnd;
            }
            double cost = BillingCalculationUtils.round(d.getDouble(FIELD_COST), 2);
            costTotal += cost;

            Document item = new Document().append(FIELD_USER_ID, id.getString(USER))
                    .append(FIELD_DLAB_ID, resourceId).append(DLAB_RESOURCE_TYPE, resourceTypeId)
                    .append(SHAPE, shapeName).append(STATUS, status)
                    .append(FIELD_PRODUCT, id.getString(FIELD_PRODUCT))
                    .append(FIELD_RESOURCE_TYPE, id.getString(FIELD_RESOURCE_TYPE))
                    .append(FIELD_COST, BillingCalculationUtils.formatDouble(cost))
                    .append(FIELD_CURRENCY_CODE, id.getString(FIELD_CURRENCY_CODE))
                    .append(USAGE_DATE_START, dateStart).append(USAGE_DATE_END, dateEnd);
            reportItems.add(item);
        }

        return new Document().append(SERVICE_BASE_NAME, settings.getServiceBaseName())
                .append(TAG_RESOURCE_ID, settings.getConfTagResourceId()).append(USAGE_DATE_START, usageDateStart)
                .append(USAGE_DATE_END, usageDateEnd).append(ITEMS, reportItems)
                .append(COST_TOTAL,
                        BillingCalculationUtils.formatDouble(BillingCalculationUtils.round(costTotal, 2)))
                .append(FIELD_CURRENCY_CODE,
                        (reportItems.isEmpty() ? null : reportItems.get(0).getString(FIELD_CURRENCY_CODE)))
                .append(FULL_REPORT, isFullReport);
    }

    private void usersToLowerCase(List<String> users) {
        if (users != null) {
            users.replaceAll(String::toLowerCase);
        }
    }

    private void addAnotherConditionsIfNecessary(List<Bson> conditions, AwsBillingFilter filter) {
        if (filter.getDlabId() != null && !filter.getDlabId().isEmpty()) {
            conditions.add(regex(FIELD_DLAB_ID, filter.getDlabId(), "i"));
        }

        if (filter.getDateStart() != null && !filter.getDateStart().isEmpty()) {
            conditions.add(gte(FIELD_USAGE_DATE, filter.getDateStart()));
        }
        if (filter.getDateEnd() != null && !filter.getDateEnd().isEmpty()) {
            conditions.add(lte(FIELD_USAGE_DATE, filter.getDateEnd()));
        }
    }

    protected void appendSsnAndEdgeNodeType(List<String> shapeNames, Map<String, BillingDAO.ShapeInfo> shapes) {
        // Add SSN and EDGE nodes
        final String ssnShape = "t2.medium";
        if (shapeNames == null || shapeNames.isEmpty() || shapeNames.contains(ssnShape)) {
            String serviceBaseName = settings.getServiceBaseName();
            shapes.put(serviceBaseName + "-ssn", new BillingDAO.ShapeInfo(ssnShape, UserInstanceStatus.RUNNING));
            FindIterable<Document> docs = getCollection(USER_EDGE).find()
                    .projection(fields(include(ID, EDGE_STATUS)));
            for (Document d : docs) {
                shapes.put(String.join("-", serviceBaseName, UsernameUtils.removeDomain(d.getString(ID)), "edge"),
                        new BillingDAO.ShapeInfo(ssnShape, UserInstanceStatus.of(d.getString(EDGE_STATUS))));
            }
        }
    }
}