Java tutorial
/************************************************************************* * (c) Copyright 2016 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; import static com.eucalyptus.util.RestrictedTypes.getIamActionByMessageType; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; import javax.inject.Inject; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.objectstorage.client.EucaS3Client; import com.eucalyptus.portal.monthlyreport.MonthlyReports; import com.eucalyptus.portal.workflow.AwsUsageRecord; import com.eucalyptus.portal.awsusage.AwsUsageRecords; import com.eucalyptus.portal.common.model.*; import org.apache.log4j.Logger; import com.eucalyptus.auth.AuthContextSupplier; import com.eucalyptus.auth.Permissions; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.portal.common.TagClient; import com.eucalyptus.portal.common.policy.PortalPolicySpec; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.TypeMappers; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; /** * */ @SuppressWarnings("unused") @ComponentNamed public class PortalService { private static final Logger logger = Logger.getLogger(PortalService.class); private static final Map<String, String> AWS_USAGE_SERVICE_MAP = ImmutableMap.<String, String>builder() .put("ec2", "AmazonEC2").put("s3", "AmazonS3").put("cloudwatch", "AmazonCloudWatch").build(); private static final Map<String, Map<String, String>> AWS_USAGE_USAGE_TYPE_MAPS = ImmutableMap .<String, Map<String, String>>builder() .put("AmazonCloudWatch", ImmutableMap.of("request", "Request-NoCharge")).build(); private final BillingAccounts billingAccounts; private final BillingInfos billingInfos; private final TagClient tagClient; @Inject public PortalService(final BillingAccounts billingAccounts, final BillingInfos billingInfos, final TagClient tagClient) { this.billingAccounts = billingAccounts; this.billingInfos = billingInfos; this.tagClient = tagClient; } public ModifyAccountResponseType modifyAccount(final ModifyAccountType request) throws PortalServiceException { final Context context = checkAuthorized(); final ModifyAccountResponseType response = request.getReply(); if (request.getUserBillingAccess() != null) { Function<BillingAccount, BillingAccount> updater = account -> { account.setUserAccessEnabled(request.getUserBillingAccess()); return account; }; try { try { response.getResult().setAccountSettings(billingAccounts.updateByAccount( context.getAccountNumber(), context.getAccount(), account -> TypeMappers.transform(updater.apply(account), AccountSettings.class))); } catch (PortalMetadataNotFoundException e) { final BillingAccount billingAccount = updater.apply(billingAccounts.defaults()); billingAccount.setOwner(context.getUserFullName()); billingAccount.setDisplayName(context.getAccountNumber()); response.getResult().setAccountSettings(billingAccounts.save(billingAccount, TypeMappers.lookupF(BillingAccount.class, AccountSettings.class))); } } catch (Exception e) { throw handleException(e); } } return response; } public ViewAccountResponseType viewAccount(final ViewAccountType request) throws PortalServiceException { final Context context = checkAuthorized(); final ViewAccountResponseType response = request.getReply(); try { response.getResult().setAccountSettings(billingAccounts.lookupByAccount(context.getAccountNumber(), context.getAccount(), TypeMappers.lookupF(BillingAccount.class, AccountSettings.class))); } catch (PortalMetadataNotFoundException e) { response.getResult() .setAccountSettings(TypeMappers.transform(billingAccounts.defaults(), AccountSettings.class)); } catch (Exception e) { throw handleException(e); } return response; } public ModifyBillingResponseType modifyBilling(final ModifyBillingType request) throws PortalServiceException { final Context context = checkAuthorized(); final ModifyBillingResponseType response = request.getReply(); Function<BillingInfo, BillingInfo> updater = info -> { info.setBillingReportsBucket(request.getReportBucket()); info.setDetailedBillingEnabled(MoreObjects.firstNonNull(request.getDetailedBillingEnabled(), false)); if (request.getActiveCostAllocationTags() != null) { info.setActiveCostAllocationTags(request.getActiveCostAllocationTags()); } return info; }; final Predicate<String> testBucket = (bucket) -> { try { final EucaS3Client s3c = BucketUploadableActivities.getS3Client(); PutObjectRequest req = new PutObjectRequest(bucket, "aws-programmatic-access-test-object", new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8)), new ObjectMetadata()) .withCannedAcl(CannedAccessControlList.BucketOwnerFullControl); s3c.putObject(req); return true; } catch (final Exception ex) { ; } return false; }; try { if (request.getReportBucket() != null && !testBucket.test(request.getReportBucket())) { throw new PortalInvalidParameterException("Requested bucket is not accessible by billing"); } try { response.getResult().setBillingSettings( billingInfos.updateByAccount(context.getAccountNumber(), context.getAccount(), info -> TypeMappers.transform(updater.apply(info), BillingSettings.class))); } catch (PortalMetadataNotFoundException e) { final BillingInfo billingInfo = updater.apply(billingInfos.defaults()); billingInfo.setOwner(context.getUserFullName()); billingInfo.setDisplayName(context.getAccountNumber()); response.getResult().setBillingSettings(billingInfos.save(billingInfo, TypeMappers.lookupF(BillingInfo.class, BillingSettings.class))); } } catch (Exception e) { throw handleException(e); } return response; } public ViewBillingResponseType viewBilling(final ViewBillingType request) throws PortalServiceException { final Context context = checkAuthorized(); final ViewBillingResponseType response = request.getReply(); try { response.getResult().setBillingSettings(billingInfos.lookupByAccount(context.getAccountNumber(), context.getAccount(), TypeMappers.lookupF(BillingInfo.class, BillingSettings.class))); } catch (PortalMetadataNotFoundException e) { response.getResult() .setBillingSettings(TypeMappers.transform(billingInfos.defaults(), BillingSettings.class)); } catch (Exception e) { throw handleException(e); } try { final Set<String> inactiveTagKeys = Sets.newTreeSet(); inactiveTagKeys .addAll(tagClient.getTagKeys(new GetTagKeysType().markPrivileged()).getResult().getKeys()); inactiveTagKeys.removeAll(response.getResult().getBillingSettings().getActiveCostAllocationTags()); response.getResult().getBillingMetadata().setInactiveCostAllocationTags( Lists.newArrayList(Ordering.from(String.CASE_INSENSITIVE_ORDER).sortedCopy(inactiveTagKeys))); } catch (Exception e) { logger.error("Error loading tag keys", e); } return response; } public ViewUsageResponseType viewUsage(final ViewUsageType request) throws PortalServiceException { final Context context = checkAuthorized(); final ViewUsageResponseType response = request.getReply(); final Function<ViewUsageType, Optional<PortalServiceException>> requestVerifier = (req) -> { final String granularity = req.getReportGranularity() != null ? req.getReportGranularity().toLowerCase() : null; if (granularity == null) { return Optional.of(new PortalInvalidParameterException("Granularity must be specified")); } if (!Sets.newHashSet("hourly", "hour", "daily", "day", "monthly", "month").contains(granularity)) { return Optional.of(new PortalInvalidParameterException( "Can't recognize granularity. Valid values are hourly, daily, and monthly")); } final String service = req.getServices() != null ? req.getServices().toLowerCase() : null; if (service == null) { return Optional.of(new PortalInvalidParameterException("Service name must be specified")); } else if (!Sets.newHashSet("ec2", "s3", "cloudwatch").contains(service)) { return Optional.of(new PortalInvalidParameterException( "Can't recognize service name. Supported services are ec2, s3, and cloudwatch")); } if (req.getTimePeriodFrom() == null) { return Optional.of(new PortalInvalidParameterException("Beginning time period must be specified")); } if (req.getTimePeriodTo() == null) { return Optional.of(new PortalInvalidParameterException("Ending time period must be specified")); } return Optional.empty(); }; final Function<ViewUsageType, ViewUsageType> requestFormatter = (req) -> { final String service = req.getServices().toLowerCase(); if (AWS_USAGE_SERVICE_MAP.containsKey(service)) { req.setServices(AWS_USAGE_SERVICE_MAP.get(service)); } final String operation = req.getOperations(); if (operation != null && "all".equals(operation.toLowerCase())) { req.setOperations(null); } if ("all".equalsIgnoreCase(request.getUsageTypes())) { req.setUsageTypes(null); } else if (AWS_USAGE_USAGE_TYPE_MAPS.containsKey(request.getServices())) { final Map<String, String> usageTypeMapForService = AWS_USAGE_USAGE_TYPE_MAPS .get(request.getServices()); if (usageTypeMapForService.containsKey(request.getUsageTypes())) { request.setUsageTypes(usageTypeMapForService.get(request.getUsageTypes())); } } return req; }; final Optional<PortalServiceException> error = requestVerifier.apply(request); if (error.isPresent()) { throw error.get(); } final ViewUsageType req = requestFormatter.apply(request); final String service = req.getServices(); final String operation = req.getOperations(); final String usageType = req.getUsageTypes(); final String granularity = req.getReportGranularity(); final Date periodBegin = req.getTimePeriodFrom(); final Date periodEnd = req.getTimePeriodTo(); final List<AwsUsageRecord> records = Lists.newArrayList(); if (granularity != null && granularity.startsWith("hour")) { records.addAll(AwsUsageRecords.getInstance().queryHourly(context.getAccountNumber(), service, operation, usageType, periodBegin, periodEnd)); } else if (granularity != null && (granularity.startsWith("day") || granularity.startsWith("dai"))) { records.addAll(AwsUsageRecords.getInstance().queryDaily(context.getAccountNumber(), service, operation, usageType, periodBegin, periodEnd)); } else if (granularity != null && granularity.startsWith("month")) { records.addAll(AwsUsageRecords.getInstance().queryMonthly(context.getAccountNumber(), service, operation, usageType, periodBegin, periodEnd)); } else { throw new PortalInvalidParameterException("Valid report granularity are hourly, daily or monthly"); } final Function<AwsUsageRecord, String> formatter = (r) -> { final StringBuilder sb = new StringBuilder(); final DateFormat df = new SimpleDateFormat("MM/dd/yy HH:mm:ss"); //Service, Operation, UsageType, Resource, StartTime, EndTime, UsageValue //AmazonEC2,Unknown,CW:AlarmMonitorUsage,,11/01/16 00:00:00,11/02/16 00:00:00,48 sb.append(r.getService() != null ? r.getService() + "," : ","); sb.append(r.getOperation() != null ? r.getOperation() + "," : ","); sb.append(r.getUsageType() != null ? r.getUsageType() + "," : ","); sb.append(r.getResource() != null ? r.getResource() + "," : ","); sb.append(r.getStartTime() != null ? df.format(r.getStartTime()) + "," : ","); sb.append(r.getEndTime() != null ? df.format(r.getEndTime()) + "," : ","); sb.append(r.getUsageValue() != null ? r.getUsageValue() : ""); return sb.toString(); }; response.setResult(new ViewUsageResult()); final StringBuilder sb = new StringBuilder(); sb.append("Service, Operation, UsageType, Resource, StartTime, EndTime, UsageValue"); final Optional<String> data = records.stream().map(formatter) .reduce((l1, l2) -> String.format("%s\n%s", l1, l2)); if (data.isPresent()) { sb.append("\n"); sb.append(data.get()); } response.getResult().setData(sb.toString()); return response; } public ViewMonthlyUsageResponseType viewMonthlyUsage(final ViewMonthlyUsageType request) throws PortalServiceException { final Context context = checkAuthorized(); final ViewMonthlyUsageResponseType response = request.getReply(); final Predicate<ViewMonthlyUsageType> requestVerifier = (req) -> { final String year = req.getYear(); final String month = req.getMonth(); if (!Pattern.matches("2[0-9][0-9][0-9]", year)) // Do EUCA exists in year 3000? return false; if (!Pattern.matches("[0-1]?[0-9]", month)) { return false; } try { final int nMonth = Integer.parseInt(month); if (!(nMonth >= 1 && nMonth <= 12)) return false; } catch (final NumberFormatException ex) { return false; } return true; }; if (!requestVerifier.test(request)) throw new PortalInvalidParameterException("Invalid year and month requested"); final String year; final String month; try { year = String.format("%d", Integer.parseInt(request.getYear())); month = String.format("%d", Integer.parseInt(request.getMonth())); } catch (final NumberFormatException ex) { throw new PortalInvalidParameterException("Invalid year and month requested"); } response.setResult(new ViewMonthlyUsageResult()); final StringBuilder sb = new StringBuilder(); sb.append( "\"InvoiceID\",\"PayerAccountId\",\"LinkedAccountId\",\"RecordType\",\"RecordID\",\"BillingPeriodStartDate\"," + "\"BillingPeriodEndDate\",\"InvoiceDate\",\"PayerAccountName\",\"LinkedAccountName\",\"TaxationAddress\"," + "\"PayerPONumber\",\"ProductCode\",\"ProductName\",\"SellerOfRecord\",\"UsageType\",\"Operation\",\"RateId\"," + "\"ItemDescription\",\"UsageStartDate\",\"UsageEndDate\",\"UsageQuantity\",\"BlendedRate\",\"CurrencyCode\"," + "\"CostBeforeTax\",\"Credits\",\"TaxAmount\",\"TaxType\",\"TotalCost\""); try { final Optional<String> data = MonthlyReports.getInstance() .lookupReport(AccountFullName.getInstance(context.getAccountNumber()), year, month).stream() .reduce((l1, l2) -> String.format("%s\n%s", l1, l2)); if (data.isPresent()) { sb.append("\n"); sb.append(data.get()); } } catch (final NoSuchElementException ex) { ; } response.getResult().setData(sb.toString()); return response; } protected static Context checkAuthorized() throws PortalServiceException { final Context ctx = Contexts.lookup(); final AuthContextSupplier requestUserSupplier = ctx.getAuthContext(); if (!Permissions.isAuthorized(PortalPolicySpec.VENDOR_PORTAL, "", "", null, getIamActionByMessageType(), requestUserSupplier)) { throw new PortalServiceUnauthorizedException("UnauthorizedOperation", "You are not authorized to perform this operation."); } return ctx; } /** * Method always throws, signature allows use of "throw handleException ..." */ private static PortalServiceException handleException(final Exception e) throws PortalServiceException { Exceptions.findAndRethrow(e, PortalServiceException.class); logger.error(e, e); final PortalServiceException exception = new PortalServiceException("InternalError", String.valueOf(e.getMessage())); if (Contexts.lookup().hasAdministrativePrivileges()) { exception.initCause(e); } throw exception; } }