cloud_cost_tracker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +16 -0
  2. data/.yardopts +1 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +10 -0
  5. data/LICENSE.TXT +10 -0
  6. data/README.md +123 -0
  7. data/Rakefile +15 -0
  8. data/billing issues.md +4 -0
  9. data/bin/cloud_cost_tracker +94 -0
  10. data/cloud_cost_tracker.gemspec +36 -0
  11. data/config/accounts.example.yml +45 -0
  12. data/config/billing/compute-aws-servers/us-east-on_demand.yml +44 -0
  13. data/config/billing/compute-aws-snapshots.yml +5 -0
  14. data/config/billing/compute-aws-volumes.yml +5 -0
  15. data/config/billing/elasticache-aws.yml +15 -0
  16. data/config/billing/rds-aws-servers/us-east-on_demand-mysql.yml +26 -0
  17. data/config/billing/rds-aws-servers/us-east-on_demand-storage.yml +10 -0
  18. data/config/billing/storage-aws-directories.yml +5 -0
  19. data/config/database.example.yml +18 -0
  20. data/db/migrate/20120118000000_create_billing_records.rb +22 -0
  21. data/db/migrate/20120119000000_create_billing_codes.rb +20 -0
  22. data/lib/cloud_cost_tracker/billing/account_billing_policy.rb +106 -0
  23. data/lib/cloud_cost_tracker/billing/compute/aws/servers.rb +30 -0
  24. data/lib/cloud_cost_tracker/billing/compute/aws/snapshots.rb +43 -0
  25. data/lib/cloud_cost_tracker/billing/compute/aws/volumes.rb +30 -0
  26. data/lib/cloud_cost_tracker/billing/elasticache/clusters.rb +33 -0
  27. data/lib/cloud_cost_tracker/billing/rds/server_storage.rb +30 -0
  28. data/lib/cloud_cost_tracker/billing/rds/servers.rb +31 -0
  29. data/lib/cloud_cost_tracker/billing/resource_billing_policy.rb +119 -0
  30. data/lib/cloud_cost_tracker/billing/storage/aws/directories.rb +47 -0
  31. data/lib/cloud_cost_tracker/coding/account_coding_policy.rb +73 -0
  32. data/lib/cloud_cost_tracker/coding/compute/aws/account_coding_policy.rb +38 -0
  33. data/lib/cloud_cost_tracker/coding/compute/aws/servers.rb +26 -0
  34. data/lib/cloud_cost_tracker/coding/compute/aws/volumes.rb +26 -0
  35. data/lib/cloud_cost_tracker/coding/rds/account_coding_policy.rb +25 -0
  36. data/lib/cloud_cost_tracker/coding/rds/servers.rb +59 -0
  37. data/lib/cloud_cost_tracker/coding/resource_coding_policy.rb +25 -0
  38. data/lib/cloud_cost_tracker/extensions/fog_model.rb +41 -0
  39. data/lib/cloud_cost_tracker/models/billing_code.rb +14 -0
  40. data/lib/cloud_cost_tracker/models/billing_record.rb +88 -0
  41. data/lib/cloud_cost_tracker/tasks.rb +3 -0
  42. data/lib/cloud_cost_tracker/tracker.rb +86 -0
  43. data/lib/cloud_cost_tracker/util/module_instrospection.rb +13 -0
  44. data/lib/cloud_cost_tracker/version.rb +3 -0
  45. data/lib/cloud_cost_tracker.rb +113 -0
  46. data/lib/tasks/database.rake +25 -0
  47. data/lib/tasks/rspec.rake +6 -0
  48. data/lib/tasks/track.rake +19 -0
  49. data/spec/lib/cloud_cost_tracker/billing/account_billing_policy_spec.rb +33 -0
  50. data/spec/lib/cloud_cost_tracker/billing/resource_billing_policy_spec.rb +102 -0
  51. data/spec/lib/cloud_cost_tracker/cloud_cost_tracker_spec.rb +57 -0
  52. data/spec/lib/cloud_cost_tracker/coding/account_coding_policy_spec.rb +32 -0
  53. data/spec/lib/cloud_cost_tracker/coding/resource_coding_policy_spec.rb +23 -0
  54. data/spec/lib/cloud_cost_tracker/models/billing_code_spec.rb +35 -0
  55. data/spec/lib/cloud_cost_tracker/models/billing_record_spec.rb +206 -0
  56. data/spec/lib/cloud_cost_tracker/tracker_spec.rb +37 -0
  57. data/spec/spec_helper.rb +58 -0
  58. data/spec/support/_configure_logging.rb +6 -0
  59. data/writing-billing-policies.md +8 -0
  60. data/writing-coding-policies.md +7 -0
  61. metadata +224 -0
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ config/accounts.yml
5
+ config/database.yml
6
+ config/policies
7
+ pkg/*
8
+ *~
9
+ .DS_Store
10
+ *.tmproj
11
+ db/*.sqlite3
12
+ log/*.log
13
+ tmp/**/*
14
+ doc
15
+ doc/**/*
16
+ .yardoc
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private --protected lib/**/*.rb - LICENSE.TXT
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cloud_cost_tracker.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec',
5
+ :version => 2,
6
+ :cli => "--color --format documentation -r ./spec/spec_helper.rb" do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ end
data/LICENSE.TXT ADDED
@@ -0,0 +1,10 @@
1
+ ----------------
2
+ *MIT License*
3
+
4
+ Copyright (c) 2012 Benton Roberts
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ Cloud Cost Tracker
2
+ ================
3
+ Records expenses for cloud computing resources in an ActiveRecord database.
4
+
5
+ *BETA VERSION - supports EC2 servers, EBS volumes, S3 buckets, and RDS servers*
6
+
7
+
8
+ ----------------
9
+ What is it?
10
+ ----------------
11
+ The Cloud Cost Tracker periodically polls one or more cloud computing accounts and determines the state of their associated cloud computing "resources": compute instances, disk volumes, stored objects, and so on. Each time an account is polled, a set of ActiveRecord objects ({CloudCostTracker::Billing::BillingRecord}s) is created or updated for each resource, containing the cost for that resource over the period of the BillingRecord.
12
+
13
+ The software can also attach "billing codes" to any resource, and to its corresponding billing records. These are arbitrary pairs of Strings which can be used to track resources for internal billing. By default, any AWS tags associated with a resource are translated into billing codes, but a framework is also provided for easily writing custom resource coding policies.
14
+
15
+
16
+ ----------------
17
+ Why is it?
18
+ ----------------
19
+ The Cloud Cost Tracker was created in response to the fact that Amazon Web Services does not provide per-resource billing information, which makes *internal* billing very difficult for organizations that make extensive use of AWS. Amazon's proposed solution -- creating, deleting, and cross-authorizing different AWS accounts for each internal billing entity -- is often not workable for organizations that need to perform these operations frequently. As a solution, this gem watches all resources across many accounts, and provides a way to define custom policies for assigning internal billing codes to all recorded expenses.
20
+
21
+ The Cloud Cost Tracker is intended to be a foundation library, on top of which more complex cloud billing / accounting applications can be built. Custom billing policies for cloud services and resources that are not yet implemented are simple to create, and are discovered automatically. Here are some currently-included default policies:
22
+
23
+ * EC2 server instance runtime costs:
24
+ {CloudCostTracker::Billing::Compute::AWS::ServerBillingPolicy}
25
+ * EBS volume storage costs:
26
+ {CloudCostTracker::Billing::Compute::AWS::VolumeBillingPolicy}
27
+ * S3 bucket storage costs:
28
+ {CloudCostTracker::Billing::Storage::AWS::DirectoryBillingPolicy}
29
+ * RDS server runtime costs:
30
+ {CloudCostTracker::Billing::AWS::RDS::ServerBillingPolicy}
31
+ * RDS server storage costs:
32
+ {CloudCostTracker::Billing::AWS::RDS::ServerStorageBillingPolicy}
33
+
34
+
35
+ ----------------
36
+ Installation
37
+ ----------------
38
+ Install the Cloud Cost Tracker gem, or list it in your `Gemfile`.
39
+
40
+ gem install cloud_cost_tracker [mysql2]
41
+
42
+
43
+ ----------------
44
+ Usage
45
+ ----------------
46
+ 1) Add the BillingRecords table into your database.
47
+ Put `require 'cloud_cost_tracker/tasks'` in your Rakefile, then run:
48
+
49
+ rake db:migrate:tracker
50
+
51
+ 2) In your Ruby app, `require` the gem, and set up an ActiveRecord connection.
52
+ In Rails, the connection is set up for you automatically on startup,
53
+ but here's an example for a non-Rails app:
54
+
55
+ require 'cloud_cost_tracker'
56
+ ActiveRecord::Base.establish_connection({
57
+ :adapter => 'mysql2', :database => 'cloud_cost_tracker',
58
+ :pool => 6 # reserve at least one connection per tracked account
59
+ })
60
+
61
+ 3) Track all accounts loaded from a YAML file.
62
+
63
+ tracker = CloudCostTracker::Tracker.new(
64
+ YAML::load(File.read 'accounts.yml'), :logger => Logger.new(STDOUT)
65
+ ).start
66
+
67
+ Here are the contents of the example file: `config/accounts.example.yml`:
68
+
69
+ AWS EC2 production account: # The account name - can be anything
70
+ :provider: AWS # This is the Fog provider Module
71
+ :service: Compute # Fog service Module. So, EC2 = Fog::Compute::AWS
72
+ :credentials:
73
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
74
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
75
+ :delay: 120 # Wait time between successive pollings (in seconds)
76
+ :exclude_resources:
77
+ - :account # No need to poll for accounts - those are listed here
78
+ - :flavors # You may or may not want EC2 server types
79
+ - :images # Takes a while to list all AMIs (works though)
80
+ AWS S3 development account:
81
+ :provider: AWS # S3 = Fog::Storage::AWS
82
+ :service: Storage
83
+ :credentials:
84
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
85
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
86
+ :delay: 150
87
+ :exclude_resources: # Pricing by Bucket is supported, but not by Object
88
+ - :files # This is required for S3 - objects can't be polled directly
89
+
90
+ The tracker will run asynchronously, with one thread per account.
91
+ Once the first account has been polled, and its resources coded,
92
+ {CloudCostTracker::Billing::BillingRecord}s will be generated for
93
+ each billable resource, and available through ActiveRecord.
94
+
95
+
96
+ ----------------
97
+ Development
98
+ ----------------
99
+ This project is still in its early stages, but most of the framework is in place.
100
+ More resource costs need to be modeled as
101
+ {CloudCostTracker::Billing::ResourceBillingPolicy} classes, but the patterns for
102
+ doing so are now laid out. For the details, see {file:writing-billing-policies.md},
103
+ {file:writing-coding-policies.md}, and the API documentation.
104
+
105
+ Helping hands are appreciated!
106
+
107
+
108
+ ----------------
109
+ *Getting started with development*
110
+
111
+ 1) Install project dependencies
112
+
113
+ gem install rake bundler
114
+
115
+ 2) Fetch the project code
116
+
117
+ git clone https://github.com/benton/cloud_cost_tracker.git
118
+ cd cloud_cost_tracker
119
+
120
+ 3) Bundle up and run the tests
121
+
122
+ bundle && rake
123
+
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # Set up bundler
2
+ %w{rubygems bundler bundler/gem_tasks}.each {|dep| require dep}
3
+ bundles = [:default]
4
+ Bundler.setup(:default)
5
+ require './lib/cloud_cost_tracker'
6
+ case CloudCostTracker.env
7
+ when 'development' then Bundler.setup(:default, :development)
8
+ when 'test' then Bundler.setup(:default, :development, :test)
9
+ end
10
+
11
+ # Load all tasks from 'lib/tasks'
12
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each {|ext| load ext}
13
+
14
+ desc 'Default: run specs.'
15
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ * EBS snapshots do not report their size, only the volume_size they came from. This makes calculating their cost impossible unless it's the first snapshot from that volume.
2
+
3
+ * EBS snapshots are stored in S3 and priced by region, but they do not report their region.
4
+
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # == Synopsis
4
+ # Uses the Fog gem to generate billing records for cloud computing resources
5
+ #
6
+ # == Usage
7
+ # cloud_cost_tracker.rb [options] DB_CONFIG_FILE.YML ACCOUNTS_CONFIG_FILE.YML
8
+ #
9
+ # == Options (all options can be put into the config file)
10
+ # -d, --delay [INTEGER] Seconds between status updates. default = 180
11
+ # -l, --log-level [LEVEL] Sets Log4r level for console output. default = INFO
12
+ # -m, --migrate Update database schema
13
+ # -h, --help Displays help message
14
+ #
15
+ # == Author
16
+ # Benton Roberts
17
+
18
+ require 'optparse'
19
+ require 'cloud_cost_tracker'
20
+ LOG_LVLS = {
21
+ "DEBUG" => ::Logger::DEBUG,
22
+ "INFO" => ::Logger::INFO,
23
+ "WARN" => ::Logger::WARN,
24
+ "ERROR" => ::Logger::ERROR,
25
+ "FATAL" => ::Logger::FATAL
26
+ }
27
+
28
+ module CloudCostTracker
29
+ class CloudCostTrackerConsoleApp
30
+
31
+ def initialize
32
+ @log = FogTracker.default_logger(STDOUT)
33
+ parse_options
34
+ @log.info "Loading database configuration from #{ARGV[0]}"
35
+ config = YAML::load(File.open(ARGV[0]))[CloudCostTracker.env]
36
+ puts "Using database #{config['database']}..."
37
+ ActiveRecord::Base.establish_connection(config)
38
+ migrate if @opts[:migrate]
39
+ @log.info "Loading account information from #{@account_file}"
40
+ @accounts = YAML::load(File.open(@account_file))
41
+ @tracker = CloudCostTracker::Tracker.new(
42
+ @accounts, {:logger => @log, :delay => @opts[:delay]}
43
+ )
44
+ end
45
+
46
+ def go
47
+ @tracker.start
48
+ while true do
49
+ sleep 30 # Loop forever
50
+ #@log.info "Total resources = #{@tracker['*::*::*::*'].count}"
51
+ end
52
+ end
53
+
54
+ def parse_options
55
+ @opts = {:log_level => 'INFO'}
56
+ optparse = OptionParser.new do |opts|
57
+ opts.banner = "Usage: tracker [options] DB_CONFIG.YML [ACCOUNTS.YML]"
58
+ opts.on('-d', '--delay SECONDS', Integer,
59
+ 'Number of seconds between status updates') do |delay|
60
+ @opts[:delay] = delay
61
+ end
62
+ opts.on('-l', '--log-level LEVEL', 'Set logging level') do |log_level|
63
+ @opts[:log_level] = log_level.upcase
64
+ end
65
+ opts.on('-m', '--migrate', 'Update database schema') do
66
+ @opts[:migrate] = true
67
+ end
68
+ opts.on('-h', '--help', 'Display this help message') do
69
+ puts opts ; exit
70
+ end
71
+ end
72
+ optparse.parse!
73
+ @log.level = LOG_LVLS[@opts[:log_level]] if LOG_LVLS[@opts[:log_level]]
74
+ if ARGV.count < 1
75
+ @log.error "A database config file must be specified"
76
+ exit 1
77
+ end
78
+ if ARGV.count < 2
79
+ @account_file = "./config/accounts.yml"
80
+ else
81
+ @account_file = ARGV[1]
82
+ end
83
+ end
84
+
85
+ def migrate
86
+ @log.info "Updating database schema..."
87
+ migration_dir = File.expand_path('../../db/migrate', __FILE__)
88
+ ActiveRecord::Migrator.migrate migration_dir
89
+ end
90
+
91
+ end
92
+ end
93
+
94
+ CloudCostTracker::CloudCostTrackerConsoleApp.new.go
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cloud_cost_tracker/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cloud_cost_tracker"
7
+ s.version = CloudCostTracker::VERSION
8
+ s.authors = ["Benton Roberts"]
9
+ s.email = ["benton@bentonroberts.com"]
10
+ s.homepage = "http://github.com/benton/cloud_cost_tracker"
11
+ s.summary = %q{Records expenses for cloud computing resources }+
12
+ %q{in an ActiveRecord database}
13
+ s.description = %q{Periodically polls one or more cloud computing accounts }+
14
+ %q{using the fog gem, and generates ActiveRecord rows }+
15
+ %q{representing BillingRecords for each discovered resource.}
16
+
17
+ s.rubyforge_project = "cloud_cost_tracker"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ # Runtime dependencies
25
+ s.add_dependency "fog_tracker", '>=0.3.9'
26
+ s.add_dependency "activerecord", '>=3'
27
+
28
+ # Development / Test dependencies
29
+ s.add_development_dependency "rake"
30
+ s.add_development_dependency "rspec"
31
+ s.add_development_dependency "yard"
32
+ s.add_development_dependency "guard"
33
+ s.add_development_dependency "guard-rspec"
34
+ s.add_development_dependency "ruby_gntp"
35
+ s.add_development_dependency "sqlite3"
36
+ end
@@ -0,0 +1,45 @@
1
+ AWS EC2 production account:
2
+ :provider: AWS
3
+ :service: Compute
4
+ :credentials:
5
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
6
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7
+ :delay: 120
8
+ :exclude_resources:
9
+ AWS S3 account:
10
+ :provider: AWS
11
+ :service: Storage
12
+ :credentials:
13
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
14
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
15
+ :delay: 120
16
+ :exclude_resources:
17
+ # - :directories # (buckets)
18
+ - :files # secondary entity - does not work yet
19
+ AWS RDS account:
20
+ :provider: AWS
21
+ :service: RDS
22
+ :credentials:
23
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
24
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
25
+ :delay: 60
26
+ :exclude_resources:
27
+ - :parameters # secondary entity - does not work yet
28
+ AWS Route 53 account:
29
+ :provider: AWS
30
+ :service: DNS
31
+ :credentials:
32
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
33
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
34
+ :delay: 60
35
+ :exclude_resources:
36
+ - :records # secondary entity - does not work yet
37
+ AWS Elasticache account:
38
+ :provider: AWS
39
+ :service: Elasticache
40
+ :credentials:
41
+ :aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
42
+ :aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
43
+ :delay: 60
44
+ :exclude_resources:
45
+
@@ -0,0 +1,44 @@
1
+ # Amazon EC2 On-demand instance pricing for the US-East region
2
+ # From http://aws.amazon.com/ec2/pricing/
3
+ ---
4
+ unix:
5
+ # Standard On-Demand Instances
6
+ m1.small: 8.5
7
+ m1.medium: 16.0
8
+ m1.large: 32.0
9
+ m1.xlarge: 64.0
10
+ # Micro On-Demand Instances
11
+ t1.micro: 2.0
12
+ # Hi-Memory On-Demand Instances
13
+ m2.xlarge: 45.0
14
+ m2.2xlarge: 90.0
15
+ m2.4xlarge: 180.0
16
+ # Hi-CPU On-Demand Instances
17
+ c1.medium: 16.5
18
+ c1.xlarge: 66.0
19
+ # Cluster Compute Instances
20
+ cc1.4xlarge: 130.0
21
+ cc1.8xlarge: 240.0
22
+ # Cluster GPU Instances
23
+ cg1.4xlarge: 210.0
24
+
25
+ windows:
26
+ # Standard On-Demand Instances
27
+ m1.small: 11.5
28
+ m1.medium: 23.0
29
+ m1.large: 46.0
30
+ m1.xlarge: 92.0
31
+ # Micro On-Demand Instances
32
+ t1.micro: 2.0
33
+ # Hi-Memory On-Demand Instances
34
+ m2.xlarge: 57.0
35
+ m2.2xlarge: 114.0
36
+ m2.4xlarge: 228.0
37
+ # Hi-CPU On-Demand Instances
38
+ c1.medium: 28.5
39
+ c1.xlarge: 114.0
40
+ # Cluster Compute Instances
41
+ cc1.4xlarge: 161.0
42
+ cc1.8xlarge: 297.0
43
+ # Cluster GPU Instances
44
+ cg1.4xlarge: 260.0
@@ -0,0 +1,5 @@
1
+ # Elastic Block Store Snapshot pricing, in cents per GB per month
2
+ # From http://aws.amazon.com/ec2/pricing/
3
+ ---
4
+ us-east-1:
5
+ 14.0 # cents per GB per month
@@ -0,0 +1,5 @@
1
+ # Elastic Block Store Volume pricing, in cents per GB per month
2
+ # From http://aws.amazon.com/ec2/pricing/
3
+ ---
4
+ us-east-1:
5
+ 10.0 # cents per GB per month
@@ -0,0 +1,15 @@
1
+ # Amazon Elasticache On-demand instance pricing by Region
2
+ # From http://aws.amazon.com/elasticache/pricing/
3
+ ---
4
+ # These numbers are in cents per hour per node in the cluster
5
+ us-east:
6
+ # Standard On-Demand Instances
7
+ cache.m1.small: 9.0
8
+ cache.m1.large: 36.0
9
+ cache.m1.xlarge: 72.0
10
+ # Hi-Memory On-Demand Instances
11
+ cache.m2.xlarge: 50.5
12
+ cache.m2.2xlarge: 101.0
13
+ cache.m2.4xlarge: 202.0
14
+ # Hi-CPU On-Demand Instances
15
+ cache.c1.xlarge: 74.0
@@ -0,0 +1,26 @@
1
+ # Amazon RDS On-demand MySQL instance pricing for the US-East region
2
+ # From http://aws.amazon.com/rds/pricing/
3
+ ---
4
+ # Standard Deployment
5
+ standard:
6
+ # DB Instance Class (On-Demand)
7
+ db.t1.micro: 2.5
8
+ db.m1.small: 10.5
9
+ db.m1.large: 41.5
10
+ db.m1.xlarge: 83.0
11
+ # High-Memory DB Instance Class
12
+ db.m2.xlarge: 58.5
13
+ db.m2.2xlarge: 117.0
14
+ db.m2.4xlarge: 234.0
15
+
16
+ # Multi-AZ Deployment
17
+ multi_az:
18
+ # DB Instance Class (On-Demand)
19
+ db.t1.micro: 5.0
20
+ db.m1.small: 21.0
21
+ db.m1.large: 83.0
22
+ db.m1.xlarge: 166.0
23
+ # High-Memory DB Instance Class
24
+ db.m2.xlarge: 117.0
25
+ db.m2.2xlarge: 234.0
26
+ db.m2.4xlarge: 468.0
@@ -0,0 +1,10 @@
1
+ # Amazon RDS storage pricing for the US-East region
2
+ # From http://aws.amazon.com/rds/pricing/
3
+ ---
4
+ # Standard Deployment
5
+ standard:
6
+ 10.0 # cents per GB per month
7
+
8
+ # Multi-AZ Deployment
9
+ multi_az:
10
+ 20.0 # cents per GB per month
@@ -0,0 +1,5 @@
1
+ # Elastic S3 bucket/object pricing, in cents per GB per month
2
+ # From http://aws.amazon.com/s3/#pricing
3
+ ---
4
+ us-east-1:
5
+ 9.3 # cents per GB per month
@@ -0,0 +1,18 @@
1
+ development:
2
+ username: root
3
+ pool: 10 # Best to use at lease one connection per tracked account
4
+ timeout: 10000 # Leave this nice and long
5
+ adapter: sqlite3
6
+ database: db/cloud_cost_tracker_development.sqlite3
7
+ test:
8
+ username: root
9
+ pool: 10 # Best to use at lease one connection per tracked account
10
+ timeout: 10000 # Leave this nice and long
11
+ adapter: sqlite3
12
+ database: db/cloud_cost_tracker_test.sqlite3
13
+ production:
14
+ username: root
15
+ pool: 10 # Best to use at lease one connection per tracked account
16
+ timeout: 10000 # Leave this nice and long
17
+ adapter: sqlite3
18
+ database: db/cloud_cost_tracker_production.sqlite3
@@ -0,0 +1,22 @@
1
+ class CreateBillingRecords < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :billing_records do |t|
4
+ t.string :provider
5
+ t.string :service
6
+ t.string :account
7
+ t.string :resource_id
8
+ t.string :billing_type
9
+ t.datetime :start_time
10
+ t.datetime :stop_time
11
+ t.decimal :cost_per_hour, :scale => 10, :precision => 20
12
+ t.decimal :total_cost, :scale => 10, :precision => 20
13
+ t.string :resource_type
14
+
15
+ t.timestamps
16
+ end
17
+ end
18
+
19
+ def self.down
20
+ drop_table :billing_records
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ class CreateBillingCodes < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :billing_codes do |t|
4
+ t.string :key, :size => 512, :null => false
5
+ t.string :value, :size => 512
6
+ end
7
+
8
+ create_table :billing_records_codes, :id => false do |t|
9
+ t.integer :billing_record_id, :null => false
10
+ t.integer :billing_code_id, :null => false
11
+ end
12
+
13
+ end
14
+
15
+ def self.down
16
+ drop_table :billing_codes
17
+ drop_table :billing_records_codes
18
+ end
19
+
20
+ end
@@ -0,0 +1,106 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ # Implements the logic for billing all resources in a single account.
4
+ # Initializes the necessary ResourceBillingPolicy objects, sorts the
5
+ # resources by policy, and calls get_cost_for_duration twice on each
6
+ # resource's policy to compute the charges.
7
+ class AccountBillingPolicy
8
+
9
+ # Creates an object that implements the default account billing policy
10
+ # @param [Array <Fog::Model>] resources the resources to bill for
11
+ # @param [Hash] options optional parameters:
12
+ # - :logger - a Ruby Logger-compatible object
13
+ def initialize(resources, options={})
14
+ @log = options[:logger] || FogTracker.default_logger
15
+ setup_resource_billing_agents(resources)
16
+ # Initialize data structures for caching costs
17
+ # the hourly cost for each resource, and billing agent
18
+ @hourly_cost = Hash.new # @hourly_cost[resource][agent]
19
+ # the total cost for each resource, and billing agent
20
+ @total_cost = Hash.new # @total_cost[resource][agent]
21
+ resources.each do |resource|
22
+ @hourly_cost[resource] ||= Hash.new
23
+ @total_cost[resource] ||= Hash.new
24
+ end
25
+ @account = resources.first.tracker_account # Save account info
26
+ end
27
+
28
+ # An initializer called by the framework once per billing cycle.
29
+ # Override this method if you need to perform high-latency operations,
30
+ # like network transactions, that should not be performed per-resource.
31
+ def setup(resources) ; end
32
+
33
+ # Defines the default method for billing all resources
34
+ def bill_for(resources)
35
+ return if resources.empty?
36
+ setup_resource_billing_agents(resources)
37
+ delay = @account[:delay].to_i
38
+ bill_end = Time.now
39
+ bill_start = @account[:preceeding_update_time] || (bill_end - delay)
40
+ bill_duration = bill_end - bill_start
41
+ # calculate the hourly and total cost for all resources, by type
42
+ @agents.keys.each do |resource_class|
43
+ collection = resources.select {|r| r.class == resource_class}
44
+ collection_name = collection.first.collection.class.name.split('::').last
45
+ @log.info "Computing costs for #{collection.size}"+
46
+ " #{collection_name} in account #{@account[:name]}"
47
+ collection.each do |resource|
48
+ @log.debug "Computing costs for #{resource.tracker_description}"+
49
+ " in account #{@account[:name]}"
50
+ @agents[resource.class].each do |billing_agent|
51
+ @total_cost[resource][billing_agent] = billing_agent.
52
+ get_cost_for_duration(resource, bill_duration).round(PRECISION)
53
+ @hourly_cost[resource][billing_agent] = billing_agent.
54
+ get_cost_for_duration(resource, SECONDS_PER_HOUR).round(PRECISION)
55
+ @log.debug "Computed costs for #{resource.tracker_description}"+
56
+ " in account #{@account[:name]}"
57
+ end
58
+ end
59
+ @log.info "Computed costs for #{collection.size}"+
60
+ " #{collection_name} in account #{@account[:name]}"
61
+ end
62
+ write_records_for(resources, bill_start, bill_end)
63
+ end
64
+
65
+ private
66
+
67
+ # Writes the billing records - @total_cost and @total_cost must be populated
68
+ # @param [Array <Fog::Model>] resources the resources to write records for
69
+ # @param [Time] start_time the start time for any new BillingRecords
70
+ def write_records_for(resources, start_time, end_time)
71
+ #ActiveRecord::Base.connection_pool.with_connection do
72
+ # Write BillingRecords for all resources, by type
73
+ @agents.keys.each do |resource_class|
74
+ collection = resources.select {|r| r.class == resource_class}
75
+ collection_name = collection.first.collection.class.name.split('::').last
76
+ @log.info "Writing billing records for #{collection.size} "+
77
+ "#{collection_name} in account #{@account[:name]}"
78
+ collection.each do |resource|
79
+ @agents[resource.class].each do |billing_agent|
80
+ billing_agent.write_billing_record_for(resource,
81
+ @hourly_cost[resource][billing_agent],
82
+ @total_cost[resource][billing_agent],
83
+ start_time, end_time
84
+ )
85
+ end
86
+ end
87
+ @log.info "Wrote billing records for #{collection.size} "+
88
+ "#{collection_name} in account #{@account[:name]}"
89
+ end
90
+ #end
91
+ end
92
+
93
+ # Builds a Hash of BillingPolicy agents, indexed by resource Class name
94
+ def setup_resource_billing_agents(resources)
95
+ @agents = Hash.new
96
+ ((resources.collect {|r| r.class}).uniq).each do |resource_class|
97
+ @agents[resource_class] = CloudCostTracker::create_billing_agents(
98
+ resource_class, {:logger => @log})
99
+ @agents[resource_class].each {|agent| agent.setup(resources)}
100
+ @agents.delete(resource_class) if @agents[resource_class].empty?
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+ end