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
@@ -0,0 +1,30 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module Compute
4
+ module AWS
5
+ # The default billing policy for Amazon EC2 server instances
6
+ class ServerBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_HOUR = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'compute-aws-servers', 'us-east-on_demand.yml'))
10
+
11
+ # Returns the storage cost for a given EC2 server
12
+ # over some duration (in seconds)
13
+ def get_cost_for_duration(ec2_server, duration)
14
+ return 0.0 if ec2_server.state =~ /(stopped|terminated)/
15
+ hourly_cost = CENTS_PER_HOUR[platform_for(ec2_server)][ec2_server.flavor_id]
16
+ (hourly_cost * duration) / SECONDS_PER_HOUR
17
+ end
18
+
19
+ # Returns either 'windows' or 'unix', based on this instance's platform
20
+ def platform_for(resource)
21
+ ('windows' == resource.platform) ? 'windows' : 'unix'
22
+ end
23
+
24
+ def billing_type ; "EC2 Instance runtime" end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module Compute
4
+ module AWS
5
+ # The default billing policy for Amazon EBS Snapshots
6
+ #
7
+ # *NOT REALLY WORKING* - AWS does not report the size
8
+ # of snapshots, so there's no way to accurately verify the cost! :(
9
+ class SnapshotBillingPolicy < ResourceBillingPolicy
10
+ # The YAML pricing data is read from config/billing
11
+ CENTS_PER_GB_PER_MONTH = YAML.load(File.read File.join(
12
+ CONSTANTS_DIR, 'compute-aws-snapshots.yml'))
13
+
14
+ # Returns the storage cost for a given EBS Snapshot
15
+ # over some duration (in seconds)
16
+ # TODO - Make an estimate based on lineage and volume size
17
+ # This code is only accurate if the snapshot is the first for its volume
18
+ def get_cost_for_duration(snapshot, duration)
19
+ return 0.0 # TEMPORARILY DISABLED UNTIL WORKAROUND IS FOUND
20
+ CENTS_PER_GB_PER_MONTH[zone(snapshot)] * snapshot.volume_size *
21
+ duration / SECONDS_PER_MONTH
22
+ end
23
+
24
+ # Follows the snapshot's volume and returns its region, and
25
+ # chops the availability zone letter from the region
26
+ def zone(resource)
27
+ volume = resource.account_resources('volumes').find do |v|
28
+ v.identity == resource.volume_id
29
+ end
30
+ if volume && volume.availability_zone
31
+ volume.availability_zone.chop
32
+ else
33
+ "us-east-1"
34
+ end
35
+ end
36
+
37
+ def billing_type ; "EBS snapshot storage" end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module Compute
4
+ module AWS
5
+ # The default billing policy for Amazon EBS Volumes
6
+ class VolumeBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_GB_PER_MONTH = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'compute-aws-volumes.yml'))
10
+
11
+ # Returns the storage cost for a given EBS Volume
12
+ # over some duration (in seconds)
13
+ def get_cost_for_duration(volume, duration)
14
+ return 0.0 if volume.state =~ /(deleting|deleted)/
15
+ CENTS_PER_GB_PER_MONTH[zone(volume)] * volume.size *
16
+ duration / SECONDS_PER_MONTH
17
+ end
18
+
19
+ # Chops the availability zone letter from the region
20
+ def zone(resource)
21
+ resource.availability_zone.chop
22
+ end
23
+
24
+ def billing_type ; "EBS volume storage" end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module AWS
4
+ module Elasticache
5
+ # The default billing policy for Amazon Elasticache Clusters
6
+ class ClusterBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_HOUR = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'elasticache-aws.yml'))
10
+
11
+ def billing_type ; "Elasticache Cluster runtime" end
12
+
13
+ # Returns the storage cost for a given Elasticache Cluster
14
+ # over some duration (in seconds)
15
+ def get_cost_for_duration(cluster, duration)
16
+ #@log.warn "Calculating cost for #{cluster.tracker_description}"
17
+ return 0.0 if cluster.status =~ /(stopped|terminated)/
18
+ hourly_cost = cluster.num_nodes *
19
+ CENTS_PER_HOUR[region(cluster)][cluster.node_type]
20
+ (hourly_cost * duration) / SECONDS_PER_HOUR
21
+ end
22
+
23
+ # Chops the availability zone letter from the availability zone
24
+ # and returns it as a string. e.g., "us-east-XXXXX" => "us-east"
25
+ def region(cluster)
26
+ cluster.zone.split('-')[0..1].join('-')
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module AWS
4
+ module RDS
5
+ # The default billing policy for Amazon RDS server storage costs
6
+ class ServerStorageBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_GB_PER_MONTH = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'rds-aws-servers', 'us-east-on_demand-storage.yml'))
10
+
11
+ # Returns the storage cost for a given RDS server
12
+ # over some duration (in seconds)
13
+ def get_cost_for_duration(rds_server, duration)
14
+ CENTS_PER_GB_PER_MONTH[zone_setting(rds_server)] *
15
+ rds_server.allocated_storage * duration / SECONDS_PER_MONTH
16
+ end
17
+
18
+ # returns either 'multi_az' or 'standard',
19
+ # depending on whether this RDS server is multi-AZ
20
+ def zone_setting(resource)
21
+ resource.multi_az ? 'multi_az' : 'standard'
22
+ end
23
+
24
+ def billing_type ; "RDS Instance storage" end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module AWS
4
+ module RDS
5
+ # The default billing policy for Amazon RDS server runtime costs
6
+ class ServerBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_HOUR = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'rds-aws-servers', 'us-east-on_demand-mysql.yml'))
10
+
11
+ # Returns the runtime cost for a given RDS server
12
+ # over some duration (in seconds)
13
+ def get_cost_for_duration(rds_server, duration)
14
+ hourly_cost =
15
+ CENTS_PER_HOUR[zone_setting(rds_server)][rds_server.flavor_id]
16
+ (hourly_cost * duration) / SECONDS_PER_HOUR
17
+ end
18
+
19
+ # returns either 'multi_az' or 'standard',
20
+ # depending on whether this RDS server is multi-AZ
21
+ def zone_setting(resource)
22
+ resource.multi_az ? 'multi_az' : 'standard'
23
+ end
24
+
25
+ def billing_type ; "RDS Instance runtime" end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,119 @@
1
+ # Abstract class, defines a generic Billing Policy that always returns 0.0 cost
2
+ module CloudCostTracker
3
+ module Billing
4
+
5
+ PRECISION = 10 # (Should match database migration precision)
6
+ # Defines a directory for holding YML pricing constants
7
+ CONSTANTS_DIR = File.join(File.dirname(__FILE__),'../../../config/billing')
8
+
9
+ # Some time and size constants
10
+
11
+ SECONDS_PER_MINUTE = 60
12
+ SECONDS_PER_HOUR = SECONDS_PER_MINUTE * 60
13
+ SECONDS_PER_DAY = SECONDS_PER_HOUR * 24
14
+ SECONDS_PER_YEAR = SECONDS_PER_DAY * 365
15
+ SECONDS_PER_MONTH = SECONDS_PER_YEAR / 12
16
+ BYTES_PER_KB = 1024
17
+ BYTES_PER_MB = BYTES_PER_KB * 1024
18
+ BYTES_PER_GB = BYTES_PER_MB * 1024
19
+
20
+ # Implements the logic for billing a single resource.
21
+ # All Billing Policies should inherit from this class, and define
22
+ # {#get_cost_for_duration}
23
+ class ResourceBillingPolicy
24
+ include CloudCostTracker
25
+
26
+ # Don't override this method - use {#setup} instead for
27
+ # one-time behavior
28
+ # @param [Hash] options optional parameters:
29
+ # - :logger - a Ruby Logger-compatible object
30
+ def initialize(options={})
31
+ @log = options[:logger] || FogTracker.default_logger
32
+ end
33
+
34
+ # An initializer called by the framework once per billling cycle.
35
+ # Override this method if you need to perform high-latency operations,
36
+ # like network transactions, that should not be performed per-resource.
37
+ def setup(resources) ; end
38
+
39
+ # Returns the cost for a particular resource over some duration in seconds.
40
+ # ALL BILLING POLICY SUBCLASSES SHOULD OVERRIDE THIS METHOD
41
+ # @param [Fog::Model] resource the resource to be billed
42
+ # @param [Integer] duration the number of seconds for this billing period.
43
+ # @return [Float] The amount the resource cost over duration seconds.
44
+ def get_cost_for_duration(resource, duration) ; 1.0 end
45
+
46
+ # Returns the default billing type for this policy.
47
+ # Override this to set a human-readable name for the policy.
48
+ # Defaults to the last part of the subclass name.
49
+ # @return [String] a description of the costs incurred under this policy.
50
+ def billing_type
51
+ self.class.name.split('::').last #(defaluts to class name)
52
+ end
53
+
54
+ # Creates or Updates a BillingRecord for this BillingPolicy's resource.
55
+ # Don't override this -- it's called once for each resource by the
56
+ # {CloudCostTracker::Billing::AccountBillingPolicy}.
57
+ # @param [Fog::Model] resource the resource for the record to be written
58
+ # @param [Float] hourly_rate the resource's hourly rate for this period
59
+ # @param [Float] total the resource's total cost for this period
60
+ # @param [Time] start_time the start time for any new BillingRecords
61
+ # @param [Time] end_time the start time for any new BillingRecords
62
+ def write_billing_record_for(resource, hourly_rate, total,
63
+ start_time, end_time)
64
+ account = resource.tracker_account
65
+ resource_type = resource.class.name.split('::').last
66
+ return if total == 0.0 # Write no record if the cost is zero
67
+ new_record = BillingRecord.new(
68
+ :provider => account[:provider],
69
+ :service => account[:service],
70
+ :account => account[:name],
71
+ :resource_id => resource.identity,
72
+ :resource_type => resource_type,
73
+ :billing_type => billing_type,
74
+ :start_time => start_time,
75
+ :stop_time => end_time,
76
+ :cost_per_hour => hourly_rate,
77
+ :total_cost => total
78
+ )
79
+ new_record.set_codes(resource.billing_codes)
80
+ # Combine BillingRecords within maximim_gap of one another
81
+ write_or_merge_with_existing(new_record, account[:delay].to_i)
82
+ end
83
+
84
+ private
85
+
86
+ # Writes 'record' into the database if:
87
+ # 1. there's no previous record for this resource + billing type; or
88
+ # 2. the previous such record differs in hourly cost or billing codes; or
89
+ # 3. the records are separated by more than maximum_time_gap
90
+ # Otherwise, `record` is merged with the previous record that matches its
91
+ # resource and billing type
92
+ def write_or_merge_with_existing(new_record, maximum_time_gap)
93
+ write_new_record = true
94
+ last_record = BillingRecord.most_recent_like(new_record)
95
+ # If the previous record for this resource/billing type has the same
96
+ # hourly rate and billing codes, just update the previous record
97
+ if last_record && last_record.overlaps_with(new_record, maximum_time_gap)
98
+ if (last_record.cost_per_hour.round(PRECISION) ==
99
+ new_record.cost_per_hour.round(PRECISION)) &&
100
+ (last_record.billing_codes == new_record.billing_codes)
101
+ @log.debug "Updating record #{last_record.id} for "+
102
+ " #{new_record.resource_type} #{new_record.resource_id}"+
103
+ " in account #{new_record.account}"
104
+ last_record.merge_with new_record
105
+ write_new_record = false
106
+ else # If the previous record has different rate or codes...
107
+ # Make the new record begin where the previous one leaves off
108
+ new_record.start_time = last_record.stop_time
109
+ end
110
+ end
111
+ if write_new_record
112
+ @log.debug "Creating new record for for #{new_record.resource_type}"+
113
+ " #{new_record.resource_id} in account #{new_record.account}"
114
+ ActiveRecord::Base.connection_pool.with_connection {new_record.save!}
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,47 @@
1
+ module CloudCostTracker
2
+ module Billing
3
+ module Storage
4
+ module AWS
5
+ # The default billing policy for Amazon S3 buckets
6
+ class DirectoryBillingPolicy < ResourceBillingPolicy
7
+ # The YAML pricing data is read from config/billing
8
+ CENTS_PER_GB_PER_MONTH = YAML.load(File.read File.join(
9
+ CONSTANTS_DIR, 'storage-aws-directories.yml'))
10
+
11
+ # Returns the runtime cost for a given S3 bucket
12
+ # over some duration (in seconds)
13
+ def get_cost_for_duration(s3_bucket, duration)
14
+ CENTS_PER_GB_PER_MONTH[zone(s3_bucket)] * total_size(s3_bucket) *
15
+ duration / SECONDS_PER_MONTH
16
+ end
17
+
18
+ # Chops the availability zone letter from the region
19
+ def zone(resource)
20
+ 'us-east-1'
21
+ end
22
+
23
+ # Returns the total size of all a bucket's objecs, in GB
24
+ def total_size(bucket)
25
+ # check for saved value
26
+ return @bucket_size[bucket] if @bucket_size[bucket]
27
+ @log.debug "Computing size for #{bucket.tracker_description}"
28
+ total_bytes = 0
29
+ bucket.files.each {|object| total_bytes += object.content_length}
30
+ @log.debug "total bytes = #{total_bytes}"
31
+ # save the total size for later
32
+ @bucket_size[bucket] = total_bytes / BYTES_PER_GB.to_f
33
+ end
34
+
35
+ # Remembers each bucket size, because iterating over S3 objects is
36
+ # slow, and get_cost_for_duration is called twice
37
+ def setup(resources)
38
+ @bucket_size = Hash.new
39
+ end
40
+
41
+ def billing_type ; "S3 Bucket storage" end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+ module CloudCostTracker
2
+ module Coding
3
+ # Implements the generic logic for attaching billing codes to all resources
4
+ # in a single account.
5
+ # Initializes the necessary ResourceCodingPolicy objects, sorts the
6
+ # resources by policy, and calls {#code} once on each resource's policy,
7
+ # to compute the billing codes as pairs of Strings.
8
+ class AccountCodingPolicy
9
+
10
+ # Creates an object that implements a coding policy
11
+ # that attaches no billing codes
12
+ # @param [Array <Fog::Model>] resources the resources to code
13
+ # @param [Hash] options optional parameters:
14
+ # - :logger - a Ruby Logger-compatible object
15
+ def initialize(resources, options={})
16
+ @log = options[:logger] || FogTracker.default_logger
17
+ setup_resource_coding_agents(resources)
18
+ end
19
+
20
+ # An initializer called by the framework once per bill coding cycle.
21
+ # Override this method if you need to perform high-latency operations,
22
+ # like network transactions, that should not be performed per-resource.
23
+ def setup(resources) ; end
24
+
25
+ # Defines the order in which resource collections are coded.
26
+ # Override this method if you need to code the resource collections
27
+ # in a particular order. Return an Array of the Fog::Model subclasses.
28
+ # @return [Array <Class>] the class names, in preferred coding order
29
+ def priority_classes ; Array.new end
30
+
31
+ # Defines an acount-wide coding strategy for coding each resource.
32
+ # Override this method if you need to write logic for attaching billing
33
+ # codes to all resources in an account, regardless of collection / type.
34
+ def attach_account_codes(resource) ; end
35
+
36
+ # Defines the default method for coding all resources.
37
+ # Attaches Billing Codes (String pairs) to resources, as @billing_codes.
38
+ # Resources whose class is in {#priority_classes} are coded first.
39
+ def code(resources)
40
+ return if resources.empty?
41
+ account = resources.first.tracker_account
42
+ @log.info "Coding account #{account[:name]}"
43
+ resources.each {|resource| attach_account_codes(resource)}
44
+ classes_to_code = priority_classes + (@agents.keys - priority_classes)
45
+ classes_to_code.delete_if {|res_class| @agents[res_class] == nil}
46
+ classes_to_code.each do |fog_model_class|
47
+ @log.info "Coding #{fog_model_class} in account #{account[:name]}"
48
+ collection = resources.select {|r| r.class == fog_model_class}
49
+ collection.each do |resource|
50
+ @agents[fog_model_class].each do |agent|
51
+ @log.debug "Coding #{resource.tracker_description}"
52
+ agent.code(resource)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # Builds a Hash of CodingPolicy agents, indexed by resource Class name
61
+ def setup_resource_coding_agents(resources)
62
+ @agents = Hash.new
63
+ ((resources.collect {|r| r.class}).uniq).each do |resource_class|
64
+ @agents[resource_class] = CloudCostTracker::create_coding_agents(
65
+ resource_class, {:logger => @log})
66
+ @agents[resource_class].each {|agent| agent.setup(resources)}
67
+ @agents.delete(resource_class) if @agents[resource_class].empty?
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,38 @@
1
+ require 'cloud_cost_tracker/coding/account_coding_policy'
2
+ module CloudCostTracker
3
+ module Coding
4
+ module Compute
5
+ module AWS
6
+ # Implements the logic for attaching billing codes to all resources
7
+ # in a single AWS EC2 account, and defines the default order in which
8
+ # EC2 resources get coded.
9
+ class AccountCodingPolicy < CloudCostTracker::Coding::AccountCodingPolicy
10
+
11
+ # Defines the order in which EC2 resources are coded.
12
+ # @return [Array <Class>] the class names, in preferred coding order
13
+ def priority_classes
14
+ [
15
+ Fog::Compute::AWS::Tag,
16
+ Fog::Compute::AWS::SecurityGroup,
17
+ Fog::Compute::AWS::KeyPair,
18
+ Fog::Compute::AWS::Server, # pulls codes from security group
19
+ Fog::Compute::AWS::Volume, # pulls codes from server
20
+ Fog::Compute::AWS::Snapshot, # pulls codes from volume
21
+ Fog::Compute::AWS::Address,
22
+ ]
23
+ end
24
+
25
+ # Translates all AWS Tags into Billing Codes
26
+ def attach_account_codes(resource)
27
+ if resource.respond_to?(:tags) and resource.tags
28
+ resource.tags.each do |key, value|
29
+ resource.code(key, value)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ module CloudCostTracker
2
+ module Coding
3
+ module Compute
4
+ module AWS
5
+ # Implements the logic for attaching billing codes to EC2 servers
6
+ class ServerCodingPolicy < ResourceCodingPolicy
7
+
8
+ # Copies all billing codes from this server's Security Group
9
+ def code(ec2_server)
10
+ ec2_server.groups.each do |group_name|
11
+ group = ec2_server.account_resources('security_groups').find do |g|
12
+ g.identity == group_name
13
+ end
14
+ if group
15
+ group.billing_codes.each do |billing_code|
16
+ ec2_server.code(billing_code[0], billing_code[1])
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module CloudCostTracker
2
+ module Coding
3
+ module Compute
4
+ module AWS
5
+ # Implements the logic for attaching billing codes to EBS Volumes
6
+ class VolumeCodingPolicy < ResourceCodingPolicy
7
+
8
+ # Copies all billing codes from any attached instance to this volume
9
+ def code(ebs_volume)
10
+ if (ebs_volume.state == "in-use") && (ebs_volume.server_id != "")
11
+ server = ebs_volume.account_resources('servers').find do |server|
12
+ server.identity == ebs_volume.server_id
13
+ end
14
+ if server
15
+ server.billing_codes.each do |server_code|
16
+ ebs_volume.code(server_code[0], server_code[1])
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module CloudCostTracker
2
+ module Coding
3
+ module RDS
4
+ module AWS
5
+ # Implements the logic for attaching billing codes to all resources
6
+ # in a single AWS RDS account, and defines the default order in which
7
+ # RDS resources get coded.
8
+ class AccountCodingPolicy < CloudCostTracker::Coding::AccountCodingPolicy
9
+
10
+ # Defines the order in which EC2 resources are coded.
11
+ # @return [Array <Class>] the class names, in preferred coding order
12
+ def priority_classes
13
+ [
14
+ Fog::AWS::RDS::SecurityGroup,
15
+ Fog::AWS::RDS::ParameterGroup,
16
+ Fog::AWS::RDS::Server, # pulls codes from security group
17
+ Fog::AWS::RDS::Snapshot, # pulls codes from server
18
+ ]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ module CloudCostTracker
2
+ module Coding
3
+ module AWS
4
+ module RDS
5
+ # Implements the logic for attaching billing codes to RDS instances
6
+ class ServerCodingPolicy < ResourceCodingPolicy
7
+
8
+ # Indexes the security and parameter groups, so that BillingCodes
9
+ # can be pulled from them quickly when code() is called
10
+ def setup(resources)
11
+ @acc_sec_groups = Hash.new # Index the security groups
12
+ resources.first.account_resources('security_groups').each do |group|
13
+ @acc_sec_groups[group.identity] = group
14
+ end
15
+ @acc_param_groups = Hash.new # Index the parameter groups
16
+ resources.first.account_resources('parameter_groups').each do |group|
17
+ @acc_param_groups[group.identity] = group
18
+ end
19
+ end
20
+
21
+ # Copies all billing codes from this server's Groups
22
+ def code(rds_server)
23
+ code_from_security_group(rds_server)
24
+ code_from_parameter_group(rds_server)
25
+ end
26
+
27
+ # Copies all billing codes from this server's Security Group
28
+ def code_from_security_group(rds_server)
29
+ if rds_server.db_security_groups
30
+ server_groups = rds_server.db_security_groups.map do |group|
31
+ @acc_sec_groups[group['DBSecurityGroupName']]
32
+ end
33
+ server_groups.each do |group|
34
+ group.billing_codes.each do |billing_code|
35
+ rds_server.code(billing_code[0], billing_code[1])
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Copies all billing codes from this server's Parameter Group
42
+ def code_from_parameter_group(rds_server)
43
+ if rds_server.db_parameter_groups
44
+ server_groups = rds_server.db_parameter_groups.map do |group|
45
+ @acc_param_groups[group['DBParameterGroupName']]
46
+ end
47
+ server_groups.each do |group|
48
+ group.billing_codes.each do |billing_code|
49
+ rds_server.code(billing_code[0], billing_code[1])
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ # Abstract class, defines a generic resource Coding Policy that attaches no codes
2
+ module CloudCostTracker
3
+ module Coding
4
+ class ResourceCodingPolicy
5
+
6
+ # Creates an object that implements a billing policy
7
+ # that attaches no billing codes
8
+ # @param [Hash] options optional parameters:
9
+ # - :logger - a Ruby Logger-compatible object
10
+ def initialize(options={})
11
+ @log = options[:logger] || FogTracker.default_logger
12
+ end
13
+
14
+ # Used by subclasses to perform setup each time an account's
15
+ # resources are coded
16
+ # High-latency operations like network transactions that are not
17
+ # per-resource should be performed here
18
+ def setup(resources) ; end
19
+
20
+ # Attaches Billing Codes (String pairs) to resource, as billing_codes
21
+ def code(resource) ; end
22
+
23
+ end
24
+ end
25
+ end