cloud_cost_tracker 0.1.0

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.
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