/// <summary> /// Creates a consolidated pricing term from a regularly formatted pricing term /// that comes directly from the price list api. Used when you've grouped the price list by /// SKU which gives pricing terms for all on demand and reserved terms in the group. This /// will identify the on demand term and then construct the reserved terms. /// </summary> /// <param name="commonSkus"></param> /// <param name="product"></param> /// <returns></returns> public static IEnumerable <ReservedInstancePricingTerm> Build(IGrouping <string, PricingTerm> commonSkus, Product product) { if (commonSkus == null) { throw new ArgumentNullException("commonSkus"); } if (product == null) { throw new ArgumentNullException("product"); } PricingTerm onDemand = commonSkus.FirstOrDefault(x => x.TermAttributes.PurchaseOption == PurchaseOption.ON_DEMAND); if (onDemand == null) { throw new KeyNotFoundException($"{product.ProductFamily} - An on demand price data term was not found for sku: {commonSkus.Key}."); } IEnumerable <PricingTerm> reservedTerms = commonSkus.Where(x => x.TermAttributes.PurchaseOption != PurchaseOption.ON_DEMAND); if (!reservedTerms.Any()) { return(Enumerable.Empty <ReservedInstancePricingTerm>()); } return(Build(product, onDemand, reservedTerms)); }
/// <summary> /// Creates a consolidated pricing term from a regularly formatted pricing term /// that comes directly from the price list API. Used when you've already identified the on /// demand pricing term and have separated out just the reserved pricing terms. /// </summary> /// <param name="product"></param> /// <param name="ondemand"></param> /// <param name="reservedterms"></param> /// <returns></returns> public static IEnumerable <ReservedInstancePricingTerm> Build(Product product, PricingTerm ondemand, IEnumerable <PricingTerm> reservedterms) { if (product == null) { throw new ArgumentNullException("product"); } if (ondemand == null) { throw new ArgumentNullException("ondemand"); } if (reservedterms == null) { throw new ArgumentNullException("reservedterms"); } if (!reservedterms.Any()) { throw new ArgumentException("You must supply at least 1 reserved term."); } double onDemandCost = -1; // Get the on demand hourly cost // DynamoDB has free tier price dimensions in the same on demand object, so make // sure we pick the first that does not have free tier in the description KeyValuePair <string, PriceDimension> dimension = ondemand.PriceDimensions.FirstOrDefault(x => !x.Value.Description.Contains(FREE_TIER, StringComparison.OrdinalIgnoreCase)); if (dimension.Value == null) { onDemandCost = 0; } else if (!Double.TryParse(dimension.Value.PricePerUnit.First().Value, out onDemandCost)) { throw new FormatException($"Could not parse the on demand price {dimension.Value.PricePerUnit.First().Value} for sku: {product.Sku}."); } string platform = GetPlatform(product); string region = RegionMapper.GetRegionFromUsageType(product.Attributes.GetValueOrDefault("usagetype")); // Only EC2 has tenancy if (!product.Attributes.TryGetValue("tenancy", out string tenancy)) { tenancy = "Shared"; } // Each pricing term will have the price dimensions for the upfront and recurring costs foreach (PricingTerm term in reservedterms) { PriceDimension upfront = term.PriceDimensions .Select(x => x.Value) .FirstOrDefault(x => !String.IsNullOrEmpty(x.Description) && x.Description.Equals("upfront fee", StringComparison.OrdinalIgnoreCase)); PriceDimension recurring = term.PriceDimensions .Select(x => x.Value) .FirstOrDefault(x => !String.IsNullOrEmpty(x.Description) && !x.Description.Equals("upfront fee", StringComparison.OrdinalIgnoreCase)); double hourlyRecurring = 0; // Only check for recurring, since some may have no upfront if (recurring == null) { // This should never happen throw new KeyNotFoundException($"The pricing term in {product.Attributes.GetValueOrDefault("servicecode")} for sku {term.Sku} and offer term code {term.OfferTermCode} did not contain a price dimension for hourly usage charges."); } else { // Parse out the rate if (!Double.TryParse(recurring.PricePerUnit.First().Value, out hourlyRecurring)) { throw new FormatException($"Could not parse the recurring price per unit of {recurring.PricePerUnit.First().Value} for sku {term.Sku}, offer term code {term.OfferTermCode}, in service {product.Attributes.GetValueOrDefault("servicecode")}."); } } double upfrontFee = 0; if (upfront != null) { // Parse out upfront fee if (!Double.TryParse(upfront.PricePerUnit.First().Value, out upfrontFee)) { throw new FormatException($"Could not parse the upfront cost of {upfront.PricePerUnit.First().Value} for sku {term.Sku}, offer term code {term.OfferTermCode}, in service {product.Attributes.GetValueOrDefault("servicecode")}."); } } string operatingSystem = String.Empty; if (product.Attributes.ContainsKey("operatingsystem")) { operatingSystem = product.Attributes.GetValueOrDefault("operatingsystem"); } else { operatingSystem = platform; } int vCPU = 0; if (product.Attributes.ContainsKey("vcpu")) { Int32.TryParse(product.Attributes.GetValueOrDefault("vcpu"), out vCPU); } double memory = 0; if (product.Attributes.ContainsKey("memory")) { string memoryString = product.Attributes.GetValueOrDefault("memory").Replace(",", ""); Match memoryMatch = _MemoryRegex.Match(memoryString); if (memoryMatch.Success) { Double.TryParse(memoryMatch.Groups[1].Value, out memory); } } string usageType = product.Attributes.GetValueOrDefault("usagetype"); string instanceType = usageType; if (product.Attributes.ContainsKey("instancetype")) { instanceType = product.Attributes.GetValueOrDefault("usagetype"); } yield return(new ReservedInstancePricingTerm( term.Sku, term.OfferTermCode, product.Attributes.GetValueOrDefault("servicecode"), platform, operatingSystem, instanceType, product.Attributes.GetValueOrDefault("operation"), usageType, tenancy, region, vCPU, memory, onDemandCost, hourlyRecurring, upfrontFee, term.TermAttributes.LeaseContractLength, term.TermAttributes.PurchaseOption, term.TermAttributes.OfferingClass, AWSPriceListApi.Model.Term.RESERVED )); } }