joyent-cloud-pricing 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d837beb7e94cad2015118a5b5cdeae6c30bb1dd3
4
- data.tar.gz: 3f0c8b423292be9202add92080f0434051f0499c
3
+ metadata.gz: 7222aa546480d563b40f139b9678c17b6855780a
4
+ data.tar.gz: 5100b881cfc5e2cf42c3862a121049d53a71b8db
5
5
  SHA512:
6
- metadata.gz: 5c434d8ed96ceb0c1344f19bd70378105782558e57963a5c7392e9a8ab26a1f953ef30fbe7943649879e0c281176c89c2aeb21a8758c21bd2a4d1c9a7c111250
7
- data.tar.gz: 0cf755a9ad4dad85a32ed5c67a9dbd7b63bb767cfd29e080c791545cd915a2e28b5f1efa8648da7130559e4d0e68e88f6992b846f907acd6a1a0fecd9f4da410
6
+ metadata.gz: 446c0daa3bfd9431ea8bbf61400ea0f269352901abe21194dfd6968cde81e540b21bb936b0534e946d61f4fcb73d5fc6b52387b85f86d04a3e9374b28c4a05e3
7
+ data.tar.gz: b5d4beb509476cbcd89671043bb7cf7fa03262d1a7d6a7117b4d75caf7e3faf4720ea8ba123d3b9cb5cc1158fee1d1d0745bb3ba6dca3796b82440eb0dcd955e
data/README.md CHANGED
@@ -114,7 +114,50 @@ commit = Joyent::Cloud::Pricing::Commit.from_yaml 'my_company/config/joyent-co
114
114
  analyzer = Joyent::Cloud::Pricing::Analyzer.new(commit, flavors)
115
115
 
116
116
  analyzer.excess_monthly_price # => monthly $$ for instances in excess of reserve
117
- analyzer.over_committed_zone_list # => list of zones in reserve, but not in reality
117
+ analyzer.over_reserved_zone_list # => list of zones in reserve, but not in reality
118
+ ```
119
+
120
+ ## Reporter
121
+
122
+ This module is used by ```knife joyent server price``` plugin to calculate pricing with and without
123
+ reserve discounts.
124
+
125
+ ```ruby
126
+ current_zone_list = %w(g3-highcpu-8-smartos g3-highcpu-8-smartos ... )
127
+ reporter = Joyent::Cloud::Pricing::Reporter.new('config/reserve-commit.yml', current_zone_list)
128
+ puts reporter.render```
129
+
130
+ Output (prices are arbitrary in this sample output):
131
+
132
+ ```bash
133
+
134
+ SUMMARY:
135
+ Total # of zones : 123
136
+ Total # of reserved zones : 98
137
+ Total # of reserved but WASTED zones : 0
138
+ Reserve Pricing Term/Duration (years) : 1
139
+
140
+ ONE TIME:
141
+ Reserve Pricing Upfront Payments : $211,717.18
142
+
143
+ MONTHLY:
144
+ Monthly Cost of Reserve Pricing Zones : $12,457.51
145
+ On Demand Resources Cost : $23,679.68
146
+ Total Monthly (reserve + on demand) : $41,087.19
147
+
148
+ YEARLY TOTALS:
149
+ With reserve discounts : $331,607.30
150
+ Without reserve discounts : $926,224.00
151
+ Savings % : 41%
152
+
153
+ UNRESERVED FLAVORS MONTHLY COST
154
+ 9 x g3-highmemory-68.375-smartos : $10,562.40
155
+ 1 x g3-highmemory-256-smartos-cc : $4,393.44
156
+ 6 x g3-standard-7.5-smartos : $1,036.80
157
+ 1 x g3-highmemory-34.25-smartos : $588.24
158
+ 4 x g3-highcpu-1.75-kvm : $365.76
159
+ 4 x g3-standard-0.5-smartos : $46.08
160
+ 1 x g3-standard-0.625-smartos : $14.40
118
161
  ```
119
162
 
120
163
  ## Command Line Tools
@@ -1,5 +1,5 @@
1
1
  ---
2
- :date: '2014-03-27 01:09:02 -0700'
2
+ :date: '2014-03-28 22:06:51 -0700'
3
3
  :pricing:
4
4
  :g3-standard-0.625-smartos: 0.02
5
5
  :g3-standard-1.75-smartos: 0.056
@@ -0,0 +1,3 @@
1
+ :g3-standard-8-smartos: 0.26
2
+ :g3-highcpu-8-smartos: 0.58
3
+ :g3-standard-0.5-smartos: 0.016
@@ -19,12 +19,13 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "nokogiri"
22
+ spec.add_dependency 'nokogiri'
23
23
  spec.add_dependency 'mixlib-cli'
24
+ spec.add_dependency 'colored'
24
25
 
25
- spec.add_development_dependency "bundler", "~> 1.5"
26
- spec.add_development_dependency "rake"
26
+ spec.add_development_dependency 'bundler', '~> 1.5'
27
+ spec.add_development_dependency 'rake'
27
28
 
28
- spec.add_development_dependency "rspec"
29
- spec.add_development_dependency "rspec-mocks"
29
+ spec.add_development_dependency 'rspec'
30
+ spec.add_development_dependency 'rspec-mocks'
30
31
  end
data/lib/pricing.rb CHANGED
@@ -6,6 +6,7 @@ module Joyent
6
6
  module Pricing
7
7
 
8
8
  PRICING_FILENAME = File.expand_path('../../config/joyent_pricing.yml', __FILE__)
9
+ LEGACY_FILENAME = File.expand_path('../../config/legacy_prices.yml', __FILE__)
9
10
  COMMIT_FILENAME = 'config/commit_pricing.yml'
10
11
  JOYENT_URL = 'http://www.joyent.com/products/compute-service/pricing'
11
12
  HOURS_PER_MONTH = 720
@@ -20,5 +21,6 @@ require "pricing/scraper"
20
21
  require "pricing/formatter"
21
22
  require "pricing/commit"
22
23
  require "pricing/analyzer"
24
+ require "pricing/reporter"
23
25
 
24
26
  require "pricing/cli"
@@ -18,7 +18,7 @@ module Joyent::Cloud::Pricing
18
18
  end
19
19
 
20
20
  # Zones that are committed, but do not exist
21
- def over_committed_zone_list
21
+ def over_reserved_zone_list
22
22
  h = {}
23
23
  zone_list.each_pair { |flavor, count| diff = count - quantity_for(flavor); h[flavor] = -diff if diff < 0 }
24
24
  h
@@ -44,8 +44,10 @@ module Joyent::Cloud::Pricing
44
44
  excess_monthly_price + commit_monthly_price
45
45
  end
46
46
 
47
+ def upfront
48
+ commit.upfront
49
+ end
47
50
 
48
- private
49
51
 
50
52
  def monthly_full_price_for zones
51
53
  total_price_for zones do |flavor|
@@ -53,6 +55,8 @@ module Joyent::Cloud::Pricing
53
55
  end
54
56
  end
55
57
 
58
+ private
59
+
56
60
  def total_price_for zones, &block
57
61
  zones.keys.inject(0) do |sum, flavor|
58
62
  sum += zones[flavor] * yield(flavor); sum
data/lib/pricing/cli.rb CHANGED
@@ -8,9 +8,9 @@ module Joyent
8
8
 
9
9
  banner 'Usage: joyent-price-helper [--commit <path-to-commit.yml] '
10
10
 
11
- option :config_file,
12
- short: '-c COMMIT_FILE',
13
- long: '--commit COMMIT_FILE',
11
+ option :commit_config,
12
+ short: '-c COMMIT_CONFIG',
13
+ long: '--commit COMMIT_CONFIG',
14
14
  description: 'Path to the config file for commit pricing (YML), default is "config/commit_pricing.yml"',
15
15
  required: false
16
16
 
@@ -27,7 +27,6 @@ module Joyent
27
27
 
28
28
  def run argv = ARGV
29
29
  parse_options(argv)
30
- raise "Not implemented just yet"
31
30
  end
32
31
 
33
32
  end
@@ -25,8 +25,30 @@ module Joyent::Cloud::Pricing
25
25
  end
26
26
 
27
27
  def monthly_price
28
+ sum_of &->(reserve) {reserve.monthly }
29
+ end
30
+
31
+ def upfront_price
32
+ sum_of &->(reserve) {reserve.prepay}
33
+ end
34
+
35
+ def total_zones
36
+ sum_of &->(reserve) {1}
37
+ end
38
+
39
+ def yearly_price
40
+ upfront_price + 12 * monthly_price
41
+ end
42
+
43
+ def years
44
+ reserves.empty? ? 0 : reserves.values.first.years
45
+ end
46
+
47
+ private
48
+
49
+ def sum_of
28
50
  reserves.values.inject(0) do |sum, reserve|
29
- sum += reserve.monthly * reserve.quantity; sum
51
+ sum += (yield(reserve)) * reserve.quantity; sum
30
52
  end
31
53
  end
32
54
 
@@ -12,7 +12,10 @@ module Joyent::Cloud::Pricing
12
12
  end
13
13
 
14
14
  def from_yaml(filename = PRICING_FILENAME)
15
- set_instance(new(YAML.load(File.read(filename))[:pricing]))
15
+ legacy_prices = YAML.load(File.read(LEGACY_FILENAME)) rescue nil
16
+ legacy_prices ||= {}
17
+ current_prices = YAML.load(File.read(filename))[:pricing]
18
+ set_instance(new(current_prices.merge(legacy_prices)))
16
19
  end
17
20
 
18
21
  def from_url(url = JOYENT_URL)
@@ -0,0 +1,89 @@
1
+ require_relative 'commit'
2
+ require 'erb'
3
+ require 'colored'
4
+
5
+ module Joyent::Cloud::Pricing
6
+ class Reporter
7
+
8
+ attr_accessor :commit, :zones_in_use, :analyzer, :formatter
9
+
10
+ def initialize(commit = COMMIT, zones_in_use = [])
11
+ @commit = case commit
12
+ when String
13
+ Joyent::Cloud::Pricing::Commit.from_yaml(commit)
14
+ when Joyent::Cloud::Pricing::Commit
15
+ commit
16
+ when nil
17
+ Joyent::Cloud::Pricing::Commit.new
18
+ else
19
+ raise NotImplementedError, "Unknown type of commit passed: #{commit.inspect}"
20
+ end
21
+
22
+ @zones_in_use = zones_in_use
23
+ @analyzer = Analyzer.new(@commit, @zones_in_use)
24
+ @formatter = Formatter.new(pricing.config)
25
+ end
26
+
27
+ def render
28
+ @r = self
29
+ @f = formatter
30
+ ERB.new(REPORT_ASCII).result(binding)
31
+ end
32
+
33
+ def reserve?
34
+ commit.reserves.size > 0
35
+ end
36
+
37
+ def zones
38
+ zones_in_use.size
39
+ end
40
+
41
+ def pricing
42
+ Configuration.instance
43
+ end
44
+
45
+ def monthly_without_commit_discount
46
+ analyzer.monthly_full_price_for(analyzer.zone_list)
47
+ end
48
+
49
+ def excess_zones
50
+ zones = analyzer.excess_zone_list.each_pair.map{|flavor, count| [ flavor, count, pricing.monthly(flavor) * count ]}.sort{|x,y| y[2] <=> x[2]}
51
+ zones
52
+ end
53
+
54
+ def excess_zone_list
55
+ excess_zones.map do |tuple|
56
+ flavor, count, monthly = tuple
57
+ sprintf(" %2d x %-36s : %20s",
58
+ count, flavor, formatter.format_price(monthly, 20).red)
59
+ end.join("\n")
60
+ end
61
+
62
+ REPORT_ASCII = <<ASCII
63
+ SUMMARY:
64
+ Total # of zones : <%= sprintf("%20d", @r.zones).blue %>
65
+ <% if @r.reserve? %> Total # of reserved zones : <%= sprintf("%20d", @r.commit.total_zones).green %>
66
+ Total # of reserved but WASTED zones : <%= sprintf("%20d", @r.analyzer.over_reserved_zone_list.size || 0).red %>
67
+ Reserve Pricing Term/Duration (years) : <%= sprintf("%20d", @r.commit.years || 0).green %>
68
+
69
+ ONE TIME:
70
+ Reserve Pricing Upfront Payments : <%= @f.format_price(@r.commit.upfront_price, 20).green %>
71
+ <% end %>
72
+ MONTHLY:
73
+ <% if @r.reserve? %> Monthly Cost of Reserve Pricing Zones : <%= @f.format_price(@r.commit.monthly_price, 20).green %>
74
+ <% end %> On Demand Resources Cost : <%= @f.format_price(@r.analyzer.excess_monthly_price, 20).red %>
75
+ <% if @r.reserve? %> Total Monthly (reserve + on demand) : <%= @f.format_price(@r.analyzer.total_monthly_price, 20).blue %><% end %>
76
+
77
+ YEARLY TOTALS:
78
+ <% if @r.reserve? %> With reserve discounts : <%= @f.format_price(@r.commit.yearly_price, 20).green %>
79
+ <% end %> Without reserve discounts : <%= @f.format_price(@r.monthly_without_commit_discount * 12, 20).red %>
80
+ <% if @r.reserve? %> Savings % : <%= (sprintf "%19d", (100 * (@r.monthly_without_commit_discount * 12 - @r.commit.yearly_price) / (@r.monthly_without_commit_discount * 12))).green %>%<% end %>
81
+
82
+ UNRESERVED FLAVORS MONTHLY COST
83
+ <%= @r.excess_zone_list %>
84
+
85
+ ASCII
86
+
87
+ end
88
+
89
+ end
@@ -1,7 +1,7 @@
1
1
  module Joyent
2
2
  module Cloud
3
3
  module Pricing
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.2"
5
5
  end
6
6
  end
7
7
  end
@@ -74,8 +74,8 @@ describe 'Joyent::Cloud::Pricing::Analyze' do
74
74
  expect(analyzer.excess_monthly_price).to eql( 6432.48 )
75
75
  end
76
76
 
77
- it '#over_committed_zone_list' do
78
- expect(analyzer.over_committed_zone_list).to eql( {:"g3-highio-60.5-smartos" => 1 })
77
+ it '#over_reserved_zone_list' do
78
+ expect(analyzer.over_reserved_zone_list).to eql( {:"g3-highio-60.5-smartos" => 1 })
79
79
  end
80
80
 
81
81
 
@@ -1,15 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ' Joyent::Cloud::Pricing::Configuration' do
4
- let(:expected_prices) { {
3
+ describe 'Joyent::Cloud::Pricing::Configuration' do
4
+ expected_prices = {
5
5
  "g3-standard-48-smartos" => 1.536,
6
6
  "g3-standard-0.625-smartos" => 0.02,
7
- "g3-standard-30-kvm" => 0.960} }
7
+ "g3-standard-30-kvm" => 0.960,
8
+ "g3-standard-8-smartos" => 0.26,
9
+ "g3-highcpu-8-smartos" => 0.58,
10
+ "g3-standard-0.5-smartos" => 0.016
11
+ }
8
12
 
9
- it "should load pricing configuration hash from YAML" do
10
- config = Joyent::Cloud::Pricing::Configuration.from_yaml 'spec/fixtures/pricing.yml'
13
+ let(:config) {
14
+ Joyent::Cloud::Pricing::Configuration.from_yaml 'spec/fixtures/pricing.yml'
15
+ }
16
+
17
+ context "#from_yaml" do
11
18
  expected_prices.keys.each do |flavor|
12
- expect(config[flavor]).to eql(expected_prices[flavor])
19
+ it "should load pricing for #{flavor}" do
20
+ expect(config[flavor]).to eql(expected_prices[flavor])
21
+ end
13
22
  end
14
23
  end
15
24
 
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Joyent::Cloud::Pricing::Reporter' do
4
+
5
+ let(:flavors) { %w(
6
+ g3-highcpu-16-smartos
7
+ g3-highcpu-16-smartos
8
+ g3-standard-30-smartos
9
+ g3-highcpu-32-smartos-cc
10
+ g3-highcpu-32-smartos-cc
11
+ g3-highcpu-32-smartos-cc
12
+ g3-highcpu-32-smartos-cc
13
+ g3-highcpu-32-smartos-cc
14
+ g3-highcpu-32-smartos-cc
15
+ g3-highcpu-32-smartos-cc
16
+ g3-highcpu-32-smartos-cc
17
+ g3-highcpu-32-smartos-cc
18
+ g3-highcpu-32-smartos-cc
19
+ g3-highcpu-32-smartos-cc
20
+ g3-highcpu-32-smartos-cc
21
+ g3-highcpu-7-smartos
22
+ g3-highcpu-7-smartos
23
+ g3-highio-60.5-smartos
24
+ g3-highio-60.5-smartos
25
+ g3-highio-60.5-smartos
26
+ g3-highio-60.5-smartos
27
+ g3-highmemory-17.125-smartos
28
+ g3-highmemory-17.125-smartos
29
+ g3-highmemory-17.125-smartos
30
+ g3-highmemory-17.125-smartos
31
+ g3-highmemory-17.125-smartos
32
+ g3-highmemory-17.125-smartos
33
+ g3-highmemory-17.125-smartos
34
+ g3-highmemory-17.125-smartos
35
+ g3-highmemory-17.125-smartos
36
+ g3-highmemory-17.125-smartos
37
+ g3-highmemory-17.125-smartos
38
+ g3-highmemory-17.125-smartos
39
+ ) }
40
+ let(:commit) { Joyent::Cloud::Pricing::Commit.from_yaml 'spec/fixtures/commit.yml' }
41
+ let(:reporter) { Joyent::Cloud::Pricing::Reporter.new(commit, flavors) }
42
+
43
+ before do
44
+ Joyent::Cloud::Pricing::Configuration.instance(true)
45
+ end
46
+
47
+ context "#initialize" do
48
+ it "should not be empty when created" do
49
+ expect(reporter).to_not be_nil
50
+ expect(reporter.analyzer).to_not be_nil
51
+ expect(reporter.zones_in_use.size).to eql(flavors.size)
52
+ end
53
+ end
54
+
55
+ context "#render" do
56
+ it "should propertly render an ERB template" do
57
+ output = reporter.render
58
+ expect(output).to_not be_nil
59
+ end
60
+
61
+ context "blank commit configuration" do
62
+ let(:commit) { Joyent::Cloud::Pricing::Commit.new }
63
+ it "should still properly render an ERB template" do
64
+ expect(commit.reserves.size).to eql(0)
65
+ output = reporter.render
66
+ expect(output).to_not be_nil
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -6,7 +6,8 @@ describe 'Joyent::Cloud::Pricing::Scraper' do
6
6
 
7
7
  let(:prices) { {"g3-standard-48-smartos" => 1.536,
8
8
  "g3-standard-0.625-smartos" => 0.02,
9
- "g3-standard-30-kvm" => 0.960} }
9
+ "g3-standard-30-kvm" => 0.960
10
+ } }
10
11
 
11
12
  before do
12
13
  @config = scraper.scrape
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: joyent-cloud-pricing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-27 00:00:00.000000000 Z
11
+ date: 2014-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colored
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -113,6 +127,7 @@ files:
113
127
  - bin/joyent-price-helper
114
128
  - config/commit_pricing.yml.example
115
129
  - config/joyent_pricing.yml
130
+ - config/legacy_prices.yml
116
131
  - joyent-cloud-pricing.gemspec
117
132
  - lib/pricing.rb
118
133
  - lib/pricing/analyzer.rb
@@ -121,6 +136,7 @@ files:
121
136
  - lib/pricing/configuration.rb
122
137
  - lib/pricing/formatter.rb
123
138
  - lib/pricing/helpers.rb
139
+ - lib/pricing/reporter.rb
124
140
  - lib/pricing/reserve.rb
125
141
  - lib/pricing/scraper.rb
126
142
  - lib/pricing/symbolize_keys.rb
@@ -132,6 +148,7 @@ files:
132
148
  - spec/pricing/commit_spec.rb
133
149
  - spec/pricing/configuration_spec.rb
134
150
  - spec/pricing/formatter_spec.rb
151
+ - spec/pricing/reporter_spec.rb
135
152
  - spec/pricing/reserve_spec.rb
136
153
  - spec/pricing/scraper_spec.rb
137
154
  - spec/spec_helper.rb
@@ -167,6 +184,7 @@ test_files:
167
184
  - spec/pricing/commit_spec.rb
168
185
  - spec/pricing/configuration_spec.rb
169
186
  - spec/pricing/formatter_spec.rb
187
+ - spec/pricing/reporter_spec.rb
170
188
  - spec/pricing/reserve_spec.rb
171
189
  - spec/pricing/scraper_spec.rb
172
190
  - spec/spec_helper.rb