/*
*
* Copyright 2013 Netflix, 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.netflix.ice.processor;
import com.google.common.collect.Maps;
import com.netflix.ice.tag.Region;
import com.netflix.ice.tag.UsageType;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Hours;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;
public class Ec2InstanceReservationPrice {
public final VersionedPrice upfrontPrice;
public final VersionedPrice hourlyPrice;
public Ec2InstanceReservationPrice() {
upfrontPrice = new VersionedPrice();
hourlyPrice = new VersionedPrice();
}
private Ec2InstanceReservationPrice(VersionedPrice upfrontPrice, VersionedPrice hourlyPrice) {
this.upfrontPrice = upfrontPrice;
this.hourlyPrice = hourlyPrice;
}
public static class VersionedPrice {
private ConcurrentSkipListMap<Long, Price> prices = new ConcurrentSkipListMap<Long, Price>();
public Price getCreatePrice(Long millis) {
Price price = prices.get(millis);
if (price == null) {
price = new Price();
prices.put(millis, price);
}
return price;
}
public Price getPrice(Long millis) {
if (millis == null)
return prices.lastEntry().getValue();
for (Map.Entry<Long, Price> entry : prices.entrySet()) {
if (entry.getKey() > millis) {
return entry.getValue();
}
}
return prices.lastEntry().getValue();
//throw new RuntimeException("Cannot find list price");
}
public double getListPrice(long millis) {
for (Map.Entry<Long, Price> entry : prices.entrySet()) {
if (entry.getKey() > millis) {
return entry.getValue().listPrice;
}
}
throw new RuntimeException("Cannot find list price");
}
}
public static class Price {
private Double listPrice = null;
private TreeMap<Long, Double> tierPrice = Maps.newTreeMap();
public void setListPrice(double listPrice) {
this.listPrice = listPrice;
}
public Double getListPrice() {
return this.listPrice;
}
public void setPrice(Long tier, Double price) {
tierPrice.put(tier, price);
}
public double getPrice(double tier) {
for (Map.Entry<Long, Double> entry : tierPrice.descendingMap().entrySet()) {
if (tier > entry.getKey()) {
return entry.getValue();
}
}
return listPrice;
}
public double getUpfrontAmortized(long startMillis, ReservationPeriod reservationPeriod, double tier) {
double price = -1;
for (Map.Entry<Long, Double> entry : tierPrice.descendingMap().entrySet()) {
if (tier > entry.getKey()) {
price = entry.getValue();
break;
}
}
if (price < 0)
price = listPrice;
DateTime start = new DateTime(startMillis, DateTimeZone.UTC);
DateTime end = start.plusYears(reservationPeriod.years);
int hours = Hours.hoursBetween(start, end).getHours();
return price / hours;
}
}
public static class Serializer {
private static void serializeVersionedPrice(DataOutput out, VersionedPrice versionedPrice) throws IOException {
out.writeInt(versionedPrice.prices.size());
for (Long millis: versionedPrice.prices.keySet()) {
out.writeLong(millis);
out.writeDouble(versionedPrice.prices.get(millis).listPrice);
}
}
private static VersionedPrice deserializeVersionedPrice(DataInput in) throws IOException {
VersionedPrice versionedPrice = new VersionedPrice();
int size = in.readInt();
for (int i = 0; i < size; i++) {
long millis = in.readLong();
double price = in.readDouble();
versionedPrice.getCreatePrice(millis).setListPrice(price);
}
return versionedPrice;
}
public static void serialize(DataOutput out, Ec2InstanceReservationPrice reservationPrice) throws IOException {
serializeVersionedPrice(out, reservationPrice.upfrontPrice);
serializeVersionedPrice(out, reservationPrice.hourlyPrice);
}
public static Ec2InstanceReservationPrice deserialize(DataInput in) throws IOException {
VersionedPrice upfrontPrice = deserializeVersionedPrice(in);
VersionedPrice hourlyPrice = deserializeVersionedPrice(in);
return new Ec2InstanceReservationPrice(upfrontPrice, hourlyPrice);
}
}
public static class Key implements Comparable<Key> {
public final Region region;
public final UsageType usageType;
public Key(Region region, UsageType usageType) {
this.region = region;
this.usageType = usageType;
}
public int compareTo(Key t) {
int result = this.region.compareTo(t.region);
if (result != 0)
return result;
result = this.usageType.compareTo(t.usageType);
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(region.toString()).append("|").append(usageType);
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
Key other = (Key)o;
return
this.region == other.region &&
this.usageType == other.usageType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.region.hashCode();
result = prime * result + this.usageType.hashCode();
return result;
}
public static class Serializer {
public static void serialize(DataOutput out, Key key) throws IOException {
out.writeUTF(key.region.toString());
UsageType.serialize(out, key.usageType);
}
public static Key deserialize(DataInput in) throws IOException {
Region region = Region.getRegionByName(in.readUTF());
UsageType usageType = UsageType.deserialize(in);
return new Key(region, usageType);
}
}
}
public static enum ReservationPeriod {
oneyear(1, "yrTerm1"),
threeyear(3, "yrTerm3");
public final int years;
public final String jsonTag;
private ReservationPeriod(int years, String jsonTag) {
this.years = years;
this.jsonTag = jsonTag;
}
}
public static enum ReservationUtilization {
LIGHT,
MEDIUM,
HEAVY,
FIXED;
public static ReservationUtilization get(String offeringType) {
if (offeringType.indexOf(" ") > 0) {
offeringType = offeringType.substring(0, offeringType.indexOf(" ")).toUpperCase();
return valueOf(offeringType);
}
else {
for (ReservationUtilization utilization: values()) {
if (offeringType.toUpperCase().startsWith(utilization.name()))
return utilization;
}
throw new RuntimeException("Unknown ReservationUtilization " + offeringType);
}
}
}
}