/************************************************************************* * (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.instanceusage; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.portal.Ec2ReportsInvalidParameterException; import com.eucalyptus.portal.common.model.InstanceUsageFilter; import com.eucalyptus.portal.common.model.InstanceUsageFilters; import com.eucalyptus.portal.workflow.InstanceLog; import com.eucalyptus.portal.workflow.InstanceTag; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Restrictions; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import static java.util.stream.Collectors.*; public abstract class InstanceLogs { private static Logger LOG = Logger.getLogger(InstanceLogs.class ); final static InstanceLogs instance = new InstanceHourRecordsEntity(); public static final InstanceLogs getInstance() { return instance; } public abstract InstanceLogBuilder newRecord(final String accountNumber); public abstract void append(final Collection<InstanceLog> records); public abstract List<InstanceHourLog> queryHourly(String accountId, Date rangeStart, Date rangeEnd, InstanceUsageFilters filters) throws Ec2ReportsInvalidParameterException; public List<InstanceHourLog> queryDaily(String accountId, Date rangeStart, Date rangeEnd, InstanceUsageFilters filters) throws Ec2ReportsInvalidParameterException { final List<InstanceHourLog> hourlyLogs = queryHourly(accountId, rangeStart, rangeEnd, filters); final Map<String, List<InstanceHourLog>> groupById = hourlyLogs.stream() .collect( groupingBy(l -> l.getInstanceId(), mapping(Function.identity(), collectingAndThen( Collectors.toList(), l -> l.stream().sorted(Comparator.comparing(InstanceHourLog::getLogTime)).collect(Collectors.toList())) ) ) ); final Map<String, List<InstanceHourLog>> dailyLog = Maps.newHashMap(); for (final String instanceId : groupById.keySet()) { dailyLog.put(instanceId, Lists.newArrayList()); final List<InstanceHourLog> instanceLogs = groupById.get(instanceId); int day = 32; // list is sorted by log time for (int i = 0; i < instanceLogs.size(); i++) { final InstanceHourLog log = instanceLogs.get(i); if (getDay(log.getLogTime()) != day) { // it's a new day // sum hours day = getDay(log.getLogTime()); long hours = log.getHours(); int j; for (j = i + 1; j < instanceLogs.size() && day == getDay(instanceLogs.get(j).getLogTime()); j++) { hours+=instanceLogs.get(j).getHours(); } log.setHours(hours); dailyLog.get(instanceId).add(log); i = j-1; } } } dailyLog.values().stream() .forEach(li -> li.stream().forEach( l -> l.setLogTime(firstHourOfDay(l.getLogTime())) ) ); // the order of records doesn't matter return dailyLog.values().stream() .flatMap(l -> l.stream()) .collect(Collectors.toList()); } public List<InstanceHourLog> queryMonthly(String accountId, Date rangeStart, Date rangeEnd, InstanceUsageFilters filters) throws Ec2ReportsInvalidParameterException { final List<InstanceHourLog> dailyLogs = queryDaily(accountId, rangeStart, rangeEnd, filters); final Map<String, List<InstanceHourLog>> groupById = dailyLogs.stream() .collect( groupingBy(l -> l.getInstanceId(), mapping(Function.identity(), collectingAndThen( Collectors.toList(), l -> l.stream().sorted(Comparator.comparing(InstanceHourLog::getLogTime)).collect(Collectors.toList())) ) ) ); final Map<String, List<InstanceHourLog>> monthlyLog = Maps.newHashMap(); for (final String instanceId : groupById.keySet()) { monthlyLog.put(instanceId, Lists.newArrayList()); final List<InstanceHourLog> instanceLogs = groupById.get(instanceId); int month = 13; // list is sorted by log time for (int i = 0; i < instanceLogs.size(); i++) { final InstanceHourLog log = instanceLogs.get(i); if (getMonth(log.getLogTime()) != month) { // it's a new month // sum hours month = getMonth(log.getLogTime()); long hours = log.getHours(); int j; for (j = i + 1; j < instanceLogs.size() && month == getMonth(instanceLogs.get(j).getLogTime()); j++) { hours+=instanceLogs.get(j).getHours(); } log.setHours(hours); monthlyLog.get(instanceId).add(log); i = j-1; } } } monthlyLog.values().stream() .forEach(li -> li.stream().forEach( l -> l.setLogTime(firstDayOfMonth(l.getLogTime())) ) ); // the order of records doesn't matter return monthlyLog.values().stream() .flatMap(l -> l.stream()) .collect(Collectors.toList()); } private static int getDay(final Date time) { final Calendar c = Calendar.getInstance(); c.setTime(time); return c.get(Calendar.DAY_OF_MONTH); } private static int getMonth(final Date time) { final Calendar c = Calendar.getInstance(); c.setTime(time); return c.get(Calendar.MONTH); } private static Date firstHourOfDay(final Date time) { final Calendar c = Calendar.getInstance(); c.setTime(time); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return c.getTime(); } private static Date firstDayOfMonth(final Date time) { final Calendar c = Calendar.getInstance(); c.setTime(time); c.set(Calendar.DAY_OF_MONTH, 1); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return c.getTime(); } public static abstract class InstanceLogBuilder { Optional<InstanceLog> instance = Optional.empty(); private InstanceLogBuilder(final String accountId) { instance = Optional.of(init()); instance.get().setAccountId(accountId); } protected abstract InstanceLog init(); public InstanceLogBuilder withInstanceType(final String instanceType) { if (this.instance.isPresent()) { this.instance.get().setInstanceType(instanceType); } return this; } public InstanceLogBuilder withPlatform(final String platform) { if (this.instance.isPresent()) { this.instance.get().setPlatform(platform); } return this; } public InstanceLogBuilder withAvailabilityZone(final String availabilityZone) { if (this.instance.isPresent()) { this.instance.get().setAvailabilityZone(availabilityZone); } return this; } public InstanceLogBuilder withRegion(final String region) { if (this.instance.isPresent()) { this.instance.get().setRegion(region); } return this; } public InstanceLogBuilder withInstanceId(final String instanceId) { if (this.instance.isPresent()) { this.instance.get().setInstanceId(instanceId); } return this; } public InstanceLogBuilder withLogTime(final Date time) { if (this.instance.isPresent()) { this.instance.get().setLogTime(time); } return this; } public InstanceLogBuilder addTag(final String tagKey, final String tagValue) { if (this.instance.isPresent()) { this.instance.get().getTags().add(new InstanceTag() { private String key; private String value; @Override public String getKey() { return this.key; } @Override public void setKey(String key) { this.key = key; } @Override public String getValue() { return this.value; } @Override public void setValue(String value) { this.value = value; } }); } return this; } public InstanceLogBuilder empty() { this.instance = Optional.empty(); return this; } public InstanceLogBuilder merge(final InstanceLogBuilder other) { if ( this.instance.isPresent() && other.instance.isPresent() ) { final InstanceLog thisObj = this.instance.get(); final InstanceLog otherObj = other.instance.get(); if (otherObj.getPlatform()!=null) { thisObj.setPlatform(otherObj.getPlatform()); } if (otherObj.getAvailabilityZone()!=null) { thisObj.setAvailabilityZone(otherObj.getAvailabilityZone()); } if (otherObj.getInstanceType()!=null) { thisObj.setInstanceType(otherObj.getInstanceType()); } if (otherObj.getTags() != null && !otherObj.getTags().isEmpty() ) { otherObj.getTags().stream().forEach( t -> thisObj.addTag(t) ); } } else { this.instance = Optional.empty(); } return this; } public Optional<InstanceLog> build() { return this.instance; } } public static class InstanceHourRecordsEntity extends InstanceLogs { @Override public InstanceLogBuilder newRecord(String accountNumber) { return new InstanceLogBuilder(accountNumber) { @Override protected InstanceLog init() { return new InstanceLogEntity(); } @Override public InstanceLogBuilder addTag(final String tagKey, final String tagValue) { if (this.instance.isPresent()) { this.instance.get().addTag(new InstanceTagEntity(tagKey, tagValue)); } return this; } }; } @Override public void append(Collection<InstanceLog> records) { try(final TransactionResource db = Entities.transactionFor(InstanceLogEntity.class )){ records.stream().forEach( r -> Entities.persist(r)); db.commit(); }catch(final Exception ex){ LOG.error("Failed to add instance usage log", ex); } } @Override public List<InstanceHourLog> queryHourly(final String accountNumber, final Date rangeStart, final Date rangeEnd, final InstanceUsageFilters filters) throws Ec2ReportsInvalidParameterException { try (final TransactionResource db = Entities.transactionFor(InstanceLogEntity.class)) { Criteria criteria = Entities.createCriteria(InstanceLogEntity.class); if (accountNumber != null) { criteria = criteria.add(Restrictions.eq("ownerAccountNumber", accountNumber)); } if (rangeStart != null) { criteria = criteria.add(Restrictions.ge("logTime", rangeStart)); } if (rangeEnd != null) { criteria = criteria.add(Restrictions.le("logTime", rangeEnd)); } if (filters!= null && filters.getMember()!=null) { final Map<String, List<InstanceUsageFilter>> filtersByType = filters.getMember().stream() .filter( f -> f.getType() != null && f.getKey() != null ) .collect(Collectors.groupingBy( InstanceUsageFilter::getType, toList())); for (final String type : filtersByType.keySet()) { Criterion criterion = null; for (final InstanceUsageFilter filter : filtersByType.get(type)) { if ("instancetype".equals(type.toLowerCase()) || "instance_type".equals(type.toLowerCase())) { criterion = criterion == null ? Restrictions.eq("instanceType", filter.getKey() ) : Restrictions.or(criterion, Restrictions.eq("instanceType", filter.getKey() ) ); } else if ("platform".equals(type.toLowerCase()) || "platforms".equals(type.toLowerCase())) { criterion = criterion == null ? Restrictions.eq("platform", filter.getKey() ) : Restrictions.or(criterion, Restrictions.eq("platform", filter.getKey() ) ); } else if ("availabilityzone".equals(type.toLowerCase()) || "availability_zone".equals(type.toLowerCase())) { criterion = criterion == null ? Restrictions.eq("availabilityZone", filter.getKey() ) : Restrictions.or(criterion, Restrictions.eq("availabilityZone", filter.getKey() ) ); } } if (criterion != null) criteria = criteria.add(criterion); } } List<InstanceLogEntity> results = (List<InstanceLogEntity>) criteria.list(); if (filters!= null && filters.getMember()!=null) { // key: tag_key, value: set of tag values final Map<String, Set<String>> tagFilters = filters.getMember().stream() .filter ( f -> f.getType() != null ) .filter( f -> "tag".equals(f.getType().toLowerCase()) || "tags".equals(f.getType().toLowerCase()) ) .collect( Collectors.groupingBy( InstanceUsageFilter::getKey, Collectors.mapping( InstanceUsageFilter::getValue, toSet() )) ); if (!tagFilters.isEmpty()) { results = results.stream() .filter(l -> l.getTags().stream() .filter(t -> tagFilters.containsKey(t.getKey()) && tagFilters.get(t.getKey()).contains(t.getValue())) .findAny() .isPresent() ).collect(Collectors.toList()); } } return results.stream() .map( l -> new InstanceHourLogImpl(l, 1)) .collect(Collectors.toList()); } catch ( final Exception ex) { LOG.error("Failed to query instance log", ex); return Lists.newArrayList(); } } } }