joyent-cloud-pricing 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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