/*************************************************************************
* (c) Copyright 2017 Hewlett Packard Enterprise Development Company LP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
************************************************************************/
package com.eucalyptus.portal.monthlyreport;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.component.annotation.ComponentNamed;
import com.eucalyptus.entities.AbstractPersistentSupport;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.portal.PortalMetadataException;
import com.eucalyptus.portal.PortalMetadataNotFoundException;
import com.eucalyptus.portal.workflow.AwsUsageRecord;
import com.eucalyptus.portal.common.PortalMetadata;
import com.eucalyptus.portal.workflow.MonthlyUsageRecord;
import com.eucalyptus.util.Exceptions;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@ComponentNamed
public class MonthlyReports extends AbstractPersistentSupport<PortalMetadata.BillingReportMetadata, MonthlyReport, PortalMetadataException> {
private static Logger LOG = Logger.getLogger( MonthlyReports.class );
private static MonthlyReports instance = new MonthlyReports();
public static MonthlyReports getInstance() {
return instance;
}
protected MonthlyReports( ) {
super( "monthly-report" );
}
@SuppressWarnings( "ThrowableResultOfMethodCallIgnored" )
@Override
protected PortalMetadataException notFoundException(String message, Throwable cause) {
final PortalMetadataNotFoundException exception = Exceptions.findCause( cause, PortalMetadataNotFoundException.class );
if ( exception != null ) {
return exception;
}
return new PortalMetadataNotFoundException( message, cause ); }
@Override
protected PortalMetadataException metadataException(String message, Throwable cause) {
final PortalMetadataException exception = Exceptions.findCause( cause, PortalMetadataException.class );
if ( exception != null ) {
return exception;
}
return new PortalMetadataException( message, cause );
}
@Override
protected MonthlyReport exampleWithOwner(OwnerFullName ownerFullName) {
final MonthlyReport report = new MonthlyReport( );
report.setOwner( ownerFullName );
return report;
}
@Override
protected MonthlyReport exampleWithName(OwnerFullName ownerFullName, String name) {
final MonthlyReport report = new MonthlyReport( );
report.setOwner( ownerFullName );
report.setDisplayName( name );
return report;
}
public MonthlyReport exampleWithYearMonth(OwnerFullName ownerFullName, String year, String month) {
final MonthlyReport example = exampleWithName(ownerFullName, String.format("%s-%s", year, month));
example.setYear(year);
example.setMonth(month);
return example;
}
public void createOrUpdate(final OwnerFullName owner, final String year, final String month, List<MonthlyReportEntry> entries) {
final MonthlyReport example = exampleWithYearMonth(owner, year, month);
try {
MonthlyReport report = null;
try (final TransactionResource db = Entities.transactionFor(MonthlyReport.class)) {
try {
report = Entities.uniqueResult(example);
} catch (final NoSuchElementException ex) {
Entities.persist(example);
db.commit();
}
}
try (final TransactionResource db = Entities.transactionFor(MonthlyReport.class)) {
report = Entities.uniqueResult(example);
final List<MonthlyReportEntry> currentRecords = report.getEntries();
for (final MonthlyReportEntry e : entries ) {
final Optional<MonthlyReportEntry> found = currentRecords.stream()
.filter(r ->
(r.getProductCode() != null ? r.getProductCode().equals(e.getProductCode()) : true) &&
(r.getOperation() != null ? r.getOperation().equals(e.getOperation()) : true) &&
(r.getUsageType() != null ? r.getUsageType().equals(e.getUsageType()): true)
).findAny();
if (found.isPresent()) {
found.get().setUsageQuantity(e.getUsageQuantity());
} else {
report.addEntry(e);
}
}
Entities.persist(report);
db.commit();
}
}catch (final Exception ex) {
LOG.error("Failed to create or update monthly report record", ex);
}
}
public List<String> lookupReport(final OwnerFullName owner, final String year, final String month)
throws NoSuchElementException {
final MonthlyReport example = exampleWithYearMonth(owner, year, month);
try (final TransactionResource db = Entities.transactionFor(MonthlyReport.class)) {
try {
final MonthlyReport report = Entities.uniqueResult(example);
return report.getEntries().stream()
.map(r -> r.toString())
.collect(Collectors.toList());
} catch (final NoSuchElementException ex) {
throw ex;
} catch (final Exception ex) {
LOG.debug("Failed to query monthly usage report", ex);
}
}
return Lists.newArrayList();
}
public static Function<AwsUsageRecord, Optional<MonthlyReportEntry>> transform = (record) -> {
final AccountFullName owner = AccountFullName.getInstance(record.getOwnerAccountNumber());
MonthlyReportEntryBuilder builder = null;
// service name changes
if ("AmazonEC2".equals(record.getService())) {
builder = MonthlyReportEntryBuilder.forEc2(owner);
} else if ("AmazonS3".equals(record.getService())) {
builder = MonthlyReportEntryBuilder.forS3(owner);
} else {
return Optional.empty();
}
final Optional<MonthlyReportEntryType> optType = MonthlyReportEntryType.getType(record);
if (!optType.isPresent()) {
return Optional.empty(); /// this aws usage record has no corresponding monthly report entry
}
final MonthlyReportEntryType entryType = optType.get();
builder = builder
.withUsageType(record.getUsageType())
.withOperation(entryType.getOperation().isPresent() ? entryType.getOperation().get() : null )
.withUsageQuantity(entryType.getQuantity(record.getUsageValue()))
.withUsageStartDate(record.getStartTime()) // that's monthly start and end date of aws usage report
.withUsageEndDate(record.getEndTime())
.withBillingPeriodStartDate(record.getStartTime())
.withBillingPeriodEndDate(record.getEndTime());
return Optional.of(builder.build());
};
public static Function<MonthlyUsageRecord, MonthlyReportEntry> instantiate = (record) -> {
final AccountFullName owner = AccountFullName.getInstance(record.getPayerAccountId());
MonthlyReportEntryBuilder builder = null;
// service name changes
if ("AmazonEC2".equals(record.getProductCode())) {
builder = MonthlyReportEntryBuilder.forEc2(owner);
} else if ("AmazonS3".equals(record.getProductCode())) {
builder = MonthlyReportEntryBuilder.forS3(owner);
}
builder = builder
.withUsageType(record.getUsageType())
.withOperation(record.getOperation())
.withUsageQuantity(record.getUsageQuantity())
.withUsageStartDate(record.getUsageStartDate()) // that's monthly start and end date of aws usage report
.withUsageEndDate(record.getUsageEndDate())
.withBillingPeriodStartDate(record.getBillingPeriodStartDate())
.withBillingPeriodEndDate(record.getBillingPeriodEndDate());
return builder.build();
};
public static class MonthlyReportEntryBuilder {
private MonthlyReportEntry entry = null;
private MonthlyReportEntryBuilder() {
this.entry = new MonthlyReportEntry();
}
public static MonthlyReportEntryBuilder withDefault() {
final MonthlyReportEntryBuilder builder = new MonthlyReportEntryBuilder();
return builder.withSellerOfRecord("Amazon Web Services, Inc.");
}
public static MonthlyReportEntryBuilder withDefault(final AccountFullName owner) {
try {
return withDefault()
.withPayerAccountId(owner.getAccountNumber())
.withPayerAccountName(Accounts.lookupAccountAliasById(owner.getAccountNumber()));
} catch (final AuthException ex) {
throw Exceptions.toUndeclared(ex);
}
}
public static MonthlyReportEntryBuilder forEc2(final AccountFullName owner) {
return withDefault(owner)
.withProductCode("AmazonEC2")
.withProductName("Amazon Elastic Compute Cloud");
}
public static MonthlyReportEntryBuilder forS3(final AccountFullName owner) {
return withDefault(owner)
.withProductCode("AmazonS3")
.withProductName("Amazon Simple Storage Service");
}
public static MonthlyReportEntryBuilder forDataTransfer(final AccountFullName owner) {
return withDefault(owner)
.withProductCode("AWSDataTransfer")
.withProductName("AWS Data Transfer");
}
public MonthlyReportEntryBuilder withPayerAccountId(final String accountId) {
this.entry.setPayerAccountId(accountId);
return this;
}
public MonthlyReportEntryBuilder withBillingPeriodStartDate(final Date date) {
this.entry.setBillingPeriodStartDate(date);
return this;
}
public MonthlyReportEntryBuilder withBillingPeriodEndDate(final Date date) {
this.entry.setBillingPeriodEndDate(date);
return this;
}
public MonthlyReportEntryBuilder withPayerAccountName(final String accountName) {
this.entry.setPayerAccountName(accountName);
return this;
}
public MonthlyReportEntryBuilder withProductCode(final String productCode) {
this.entry.setProductCode(productCode);
return this;
}
public MonthlyReportEntryBuilder withProductName(final String productName) {
this.entry.setProductName(productName);
return this;
}
public MonthlyReportEntryBuilder withSellerOfRecord(final String sellerOfRecord) {
this.entry.setSellerOfRecord(sellerOfRecord);
return this;
}
public MonthlyReportEntryBuilder withUsageType(final String usageType) {
this.entry.setUsageType(usageType);
return this;
}
public MonthlyReportEntryBuilder withUsageStartDate(final Date date) {
this.entry.setUsageStartDate(date);
return this;
}
public MonthlyReportEntryBuilder withUsageEndDate(final Date date) {
this.entry.setUsageEndDate(date);
return this;
}
public MonthlyReportEntryBuilder withUsageQuantity(final Double quantity) {
this.entry.setUsageQuantity(quantity);
return this;
}
public MonthlyReportEntryBuilder withOperation(final String operation) {
this.entry.setOperation(operation);
return this;
}
public MonthlyReportEntry build() {
return this.entry;
}
}
}