dynamo-autoscale 0.3.6 → 0.4.1

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.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  config/dynamo-autoscale.yml
2
2
  data/
3
3
  pkg/
4
+ coverage/
data/Gemfile CHANGED
@@ -11,4 +11,5 @@ end
11
11
 
12
12
  group :test do
13
13
  gem 'rspec'
14
+ gem 'simplecov'
14
15
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamo-autoscale (0.3.6)
4
+ dynamo-autoscale (0.4.1)
5
5
  activesupport
6
6
  aws-sdk
7
7
  colored
@@ -12,28 +12,28 @@ PATH
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- activesupport (4.0.0)
15
+ activesupport (4.0.2)
16
16
  i18n (~> 0.6, >= 0.6.4)
17
17
  minitest (~> 4.2)
18
18
  multi_json (~> 1.3)
19
19
  thread_safe (~> 0.1)
20
20
  tzinfo (~> 0.3.37)
21
21
  atomic (1.1.14)
22
- aws-sdk (1.18.0)
22
+ aws-sdk (1.19.0)
23
23
  json (~> 1.4)
24
- nokogiri (< 1.6.0)
24
+ nokogiri (>= 1.4.4, < 1.6.0)
25
25
  uuidtools (~> 2.1)
26
26
  bond (0.4.3)
27
27
  coderay (1.0.9)
28
28
  colored (1.2)
29
29
  diff-lcs (1.2.4)
30
- i18n (0.6.5)
31
- json (1.8.0)
30
+ i18n (0.6.9)
31
+ json (1.8.1)
32
32
  mail (2.5.4)
33
33
  mime-types (~> 1.16)
34
34
  treetop (~> 1.4.8)
35
35
  method_source (0.8.1)
36
- mime-types (1.23)
36
+ mime-types (1.25)
37
37
  minitest (4.7.5)
38
38
  multi_json (1.8.0)
39
39
  nokogiri (1.5.10)
@@ -57,6 +57,10 @@ GEM
57
57
  diff-lcs (>= 1.1.3, < 2.0)
58
58
  rspec-mocks (2.13.1)
59
59
  ruby-prof (0.13.0)
60
+ simplecov (0.7.1)
61
+ multi_json (~> 1.0)
62
+ simplecov-html (~> 0.7.1)
63
+ simplecov-html (0.7.1)
60
64
  slop (3.4.5)
61
65
  thread_safe (0.1.3)
62
66
  atomic
@@ -64,7 +68,7 @@ GEM
64
68
  treetop (1.4.14)
65
69
  polyglot
66
70
  polyglot (>= 0.3.1)
67
- tzinfo (0.3.37)
71
+ tzinfo (0.3.38)
68
72
  uuidtools (2.1.4)
69
73
 
70
74
  PLATFORMS
@@ -76,4 +80,5 @@ DEPENDENCIES
76
80
  rake
77
81
  ripl
78
82
  rspec
83
+ simplecov
79
84
  timecop
data/README.md CHANGED
@@ -78,7 +78,11 @@ tables only.
78
78
  :aws:
79
79
  :access_key_id: "your_id"
80
80
  :secret_access_key: "your_key"
81
- :dynamo_db_endpoint: "dynamodb.us-east-1.amazonaws.com"
81
+
82
+ # Currently, dynamo-autoscale can only operate in one region at a time. If you
83
+ # want to track tables in multiple regions, you will have to run multiple
84
+ # instances of dynamo-autoscale.
85
+ :region: "us-east-1"
82
86
 
83
87
  # There are some example rulesets in the rulesets/ directory of this project.
84
88
  :ruleset: "path_to_your_ruleset.rb"
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'optparse'
4
4
 
5
+ USAGE_PATH = File.join(File.dirname(__FILE__), '..', 'USAGE')
6
+
5
7
  # This OptionParser block is here to ensure the queries to --version are
6
8
  # actually fast and don't have to go through all of the Ruby requiring lark.
7
9
  begin
@@ -13,55 +15,64 @@ begin
13
15
  end
14
16
 
15
17
  opts.on('--help', 'Shows this documentation.') do
16
- puts File.read(File.join(File.dirname(__FILE__), '..', 'USAGE'))
18
+ puts File.read(USAGE_PATH)
17
19
  exit 0
18
20
  end
19
21
  end.parse!
20
22
  rescue OptionParser::InvalidOption => e
21
- STDERR.puts e.message
22
- STDERR.puts
23
-
24
- STDERR.puts File.read(File.join(File.dirname(__FILE__), '..', 'USAGE'))
23
+ STDERR.puts("#{e.message}\n\n#{File.read(USAGE_PATH)}")
25
24
  exit 1
26
25
  end
27
26
 
28
- require_relative '../config/environment/common'
27
+ begin
28
+ require_relative '../config/environment/common'
29
+ rescue DynamoAutoscale::Error::InvalidConfigurationError => e
30
+ STDERR.puts e.message
31
+ exit 1
32
+ end
29
33
 
34
+ # Typing out DynamoAutoscale so many times is tedious.
35
+ DA = DynamoAutoscale
30
36
 
31
37
  if ARGV[0]
32
- DynamoAutoscale.setup_from_config(ARGV[0])
38
+ begin
39
+ DA.setup_from_config(ARGV[0])
40
+ rescue DA::Error::InvalidConfigurationError => e
41
+ STDERR.puts e.message
42
+ exit 1
43
+ end
33
44
  elsif ARGV[0].nil?
34
- STDERR.puts File.read(File.join(File.dirname(__FILE__), '..', 'USAGE'))
45
+ STDERR.puts File.read(USAGE_PATH)
35
46
 
36
47
  exit 1
37
48
  elsif ARGV[0] and !File.exists?(ARGV[0])
38
49
  STDERR.puts "Error: The path you specified is to a file that does not exist."
39
50
  STDERR.puts
40
- STDERR.puts File.read(File.join(File.dirname(__FILE__), '..', 'USAGE'))
51
+ STDERR.puts File.read(USAGE_PATH)
41
52
 
42
53
  exit 1
43
54
  end
44
55
 
45
- DynamoAutoscale.logger.info "Ensuring tables exist in DynamoDB..."
56
+ DA.logger.info "Ensuring tables exist in DynamoDB..."
46
57
  dynamo = AWS::DynamoDB.new
47
58
 
48
- DynamoAutoscale.poller_opts[:tables].select! do |table_name|
59
+ DA.poller_opts[:tables].select! do |table_name|
49
60
  if dynamo.tables[table_name].exists?
50
61
  true
51
62
  else
52
- DynamoAutoscale.logger.error "Table #{table_name} does not exist inside your DynamoDB."
63
+ DA.logger.error "Table #{table_name} does not exist inside your DynamoDB."
53
64
  false
54
65
  end
55
66
  end
56
67
 
57
- DynamoAutoscale.poller_class = DynamoAutoscale::CWPoller
68
+ DA.poller_class = DA::CWPoller
58
69
 
59
- unless DynamoAutoscale.config[:dry_run]
60
- DynamoAutoscale.actioner_class = DynamoAutoscale::DynamoActioner
70
+ unless DA.config[:dry_run]
71
+ DA.actioner_class = DA::DynamoActioner
61
72
  end
62
73
 
63
- DynamoAutoscale.logger.info "Finished setup. Backdating..."
64
- DynamoAutoscale.poller.backdate
74
+ DA.logger.info "Finished setup. Backdating..."
75
+ DA.poller.backdate
65
76
 
66
- DynamoAutoscale.logger.info "Starting polling loop..."
67
- DynamoAutoscale.poller.run
77
+ DA.logger.info "Starting polling loop..."
78
+ DA.poller.run
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'optparse'
2
3
  require 'fileutils'
3
4
  require 'time'
4
5
  require 'csv'
@@ -18,14 +19,32 @@ require_relative '../../lib/dynamo-autoscale/actioner'
18
19
  module DynamoAutoscale
19
20
  include DynamoAutoscale::Logger
20
21
 
21
- DEFAULT_AWS_REGION = 'us-east-1'
22
+ module Error
23
+ InvalidConfigurationError = Class.new(StandardError)
24
+ end
22
25
 
23
26
  def self.root
24
- File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
27
+ @@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
28
+ end
29
+
30
+ def self.root_dir *args
31
+ File.join(self.root, *args)
32
+ end
33
+
34
+ def self.data_dir *args
35
+ root_dir 'data', *args
36
+ end
37
+
38
+ def self.config_dir *args
39
+ root_dir 'config', *args
25
40
  end
26
41
 
27
- def self.data_dir
28
- File.join(self.root, 'data')
42
+ def self.rlib_dir *args
43
+ root_dir 'rlib', *args
44
+ end
45
+
46
+ def self.template_dir *args
47
+ root_dir 'templates', *args
29
48
  end
30
49
 
31
50
  def self.config
@@ -36,18 +55,29 @@ module DynamoAutoscale
36
55
  @@config = new_config
37
56
  end
38
57
 
58
+ def self.require_in_order *files
59
+ expand_paths(*files).each { |path| require path }
60
+ end
61
+
62
+ def self.load_in_order *files
63
+ expand_paths(*files).each { |path| load path }
64
+ end
65
+
39
66
  def self.setup_from_config path, overrides = {}
40
67
  logger.debug "[setup] Loading config..."
41
68
  self.config = YAML.load_file(path).merge(overrides)
42
69
 
43
70
  if config[:tables].nil? or config[:tables].empty?
44
- STDERR.puts "You need to specify at least one table in your config's " +
45
- ":tables section."
46
-
47
- exit 1
71
+ raise Error::InvalidConfigurationError.new("You need to specify at " +
72
+ "least one table in your config's :tables section.")
48
73
  end
49
74
 
50
- filters = config[:dry_run] ? DynamoAutoscale::LocalActioner.faux_provisioning_filters : []
75
+ filters = if config[:dry_run]
76
+ DynamoAutoscale::LocalActioner.faux_provisioning_filters
77
+ else
78
+ []
79
+ end
80
+
51
81
  if filters.empty?
52
82
  logger.debug "[setup] Not running as a dry run. Hitting production Dynamo."
53
83
  else
@@ -89,14 +119,11 @@ module DynamoAutoscale
89
119
  DynamoAutoscale.load_services
90
120
  end
91
121
 
92
- def self.require_all path
93
- Dir[File.join(root, path, '*.rb')].each { |file| require file }
94
- end
95
-
96
122
  def self.load_services
97
- Dir[File.join(DynamoAutoscale.root, 'config', 'services', '*.rb')].each do |path|
98
- load path
99
- end
123
+ load_in_order(
124
+ 'config/services/logger.rb',
125
+ 'config/services/*.rb'
126
+ )
100
127
  end
101
128
 
102
129
  def self.dispatcher= new_dispatcher
@@ -153,6 +180,10 @@ module DynamoAutoscale
153
180
  end
154
181
  end
155
182
 
183
+ def self.actioners= new_actioners
184
+ @@actioners = new_actioners
185
+ end
186
+
156
187
  def self.reset_tables
157
188
  @@tables = Hash.new { |h, k| h[k] = TableTracker.new(k) }
158
189
  end
@@ -180,9 +211,33 @@ module DynamoAutoscale
180
211
  def self.current_table
181
212
  @@current_table ||= nil
182
213
  end
214
+
215
+ private
216
+
217
+ # Expands strings given to it as paths relative to the project root
218
+ def self.expand_paths *files
219
+ files.inject([]) do |memo, path|
220
+ full_path = root_dir(path)
221
+
222
+ if (paths = Dir.glob(full_path)).length > 0
223
+ memo += paths.select { |p| File.file?(p) }
224
+ elsif File.exist?("#{full_path}.rb")
225
+ memo << "#{full_path}.rb"
226
+ elsif File.exist?(full_path)
227
+ memo << full_path
228
+ else
229
+ logger.warn "[load] Could not load file #{full_path}"
230
+ STDERR.puts Kernel.caller
231
+ exit 1
232
+ end
233
+
234
+ memo
235
+ end
236
+ end
183
237
  end
184
238
 
185
- DynamoAutoscale.require_all 'lib/dynamo-autoscale'
186
- DynamoAutoscale.require_all 'lib/dynamo-autoscale/ext/**'
239
+ DynamoAutoscale.require_in_order(
240
+ 'lib/dynamo-autoscale/**.rb',
241
+ )
187
242
 
188
243
  DynamoAutoscale.load_services
@@ -1,6 +1,29 @@
1
1
  ENV['RACK_ENV'] = "test"
2
+
3
+ # SimpleCov seems to get confused when you load a file into your code multiple
4
+ # times. It will wipe all of its current data about that file when it gets
5
+ # reloaded, so some of our coverage stats are less than they should be.
6
+ require 'simplecov'
7
+ SimpleCov.start
8
+
2
9
  require_relative 'common'
3
10
  require 'timecop'
4
11
 
5
- path = File.join(DynamoAutoscale.root, 'config', 'dynamo-autoscale-test.yml')
6
- DynamoAutoscale.setup_from_config(path)
12
+ TEST_CONFIG_PATH = DynamoAutoscale.config_dir('dynamo-autoscale-test.yml')
13
+ DynamoAutoscale.setup_from_config(TEST_CONFIG_PATH)
14
+
15
+ DynamoAutoscale.require_in_order(
16
+ 'spec/helpers/**.rb'
17
+ )
18
+
19
+ RSpec.configure do |config|
20
+ config.include DynamoAutoscale::Helpers::LoggerHelper
21
+ config.include DynamoAutoscale::Helpers::EnvironmentHelper
22
+
23
+ config.before(:each) do
24
+ DynamoAutoscale.reset_tables
25
+ DynamoAutoscale.dispatcher = nil
26
+ DynamoAutoscale.actioners = nil
27
+ DynamoAutoscale.poller = nil
28
+ end
29
+ end
@@ -1,3 +1,19 @@
1
- if DynamoAutoscale.config[:aws]
2
- AWS.config(DynamoAutoscale.config[:aws])
1
+ if config = DynamoAutoscale.config[:aws]
2
+ valid_regions = [
3
+ "us-east-1", "us-west-1", "us-west-2", "eu-west-1", "ap-southeast-1",
4
+ "ap-southeast-2", "ap-northeast-1", "sa-east-1",
5
+ ]
6
+
7
+ unless config[:region]
8
+ raise DynamoAutoscale::Error::InvalidConfigurationError.new("You must " +
9
+ "specify a :region key in the :aws section of your dynamo-autoscale " +
10
+ "configuration file!")
11
+ end
12
+
13
+ unless valid_regions.include?(config[:region])
14
+ DynamoAutoscale::Logger.logger.warn "Specified region \"#{config[:region]}\"" +
15
+ " does not appear in the valid list of regions. Proceed with caution."
16
+ end
17
+
18
+ AWS.config(config)
3
19
  end
@@ -1,31 +1,30 @@
1
- config = DynamoAutoscale.config[:logger] || {}
1
+ if config = DynamoAutoscale.config[:logger]
2
+ if config[:sync]
3
+ STDOUT.sync = true
4
+ STDERR.sync = true
5
+ end
2
6
 
3
- if config[:sync]
4
- STDOUT.sync = true
5
- STDERR.sync = true
6
- end
7
+ if config[:log_to]
8
+ STDOUT.reopen(config[:log_to])
9
+ STDERR.reopen(config[:log_to])
10
+ end
7
11
 
8
- if config[:log_to]
9
- STDOUT.reopen(config[:log_to])
10
- STDERR.reopen(config[:log_to])
11
- end
12
+ DynamoAutoscale::Logger.logger = ::Logger.new(STDOUT)
12
13
 
13
- DynamoAutoscale::Logger.logger = ::Logger.new(STDOUT)
14
+ if config[:style] == "pretty"
15
+ DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::PrettyFormatter.new
16
+ else
17
+ DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::StandardFormatter.new
18
+ end
14
19
 
15
- if config[:style] == "pretty"
16
- DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::PrettyFormatter.new
17
- else
18
- DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::StandardFormatter.new
20
+ if config[:level]
21
+ DynamoAutoscale::Logger.logger.level = ::Logger.const_get(config[:level])
22
+ end
19
23
  end
20
24
 
21
25
  if ENV['DEBUG']
22
26
  DynamoAutoscale::Logger.logger.level = ::Logger::DEBUG
23
- elsif config[:level]
24
- DynamoAutoscale::Logger.logger.level = ::Logger.const_get(config[:level])
25
- end
26
-
27
- if ENV['SILENT']
27
+ AWS.config(logger: DynamoAutoscale::Logger.logger)
28
+ elsif ENV['SILENT']
28
29
  DynamoAutoscale::Logger.logger.level = ::Logger::FATAL
29
30
  end
30
-
31
- AWS.config(logger: DynamoAutoscale::Logger.logger) if ENV['DEBUG']
@@ -76,6 +76,7 @@ module DynamoAutoscale
76
76
 
77
77
  def downscales new_val = nil
78
78
  check_day_reset!
79
+ @downscales = new_val if new_val
79
80
  @downscales
80
81
  end
81
82
 
@@ -86,13 +86,14 @@ module DynamoAutoscale
86
86
  aws_description = self.class.describe_table(table)
87
87
  decreases_today = aws_description[:provisioned_throughput][:number_of_decreases_today]
88
88
 
89
- downscales(table, decreases_today)
89
+ downscales(decreases_today)
90
90
  logger.warn "[#{e.class}] #{e.message}"
91
91
  return false
92
92
  end
93
93
 
94
94
  def self.describe_table table
95
- data = AWS::DynamoDB::ClientV2.new.describe_table(table_name: table.name)
95
+ client = AWS::DynamoDB::Client.new(:api_version => '2012-08-10')
96
+ data = client.describe_table(table_name: table.name)
96
97
 
97
98
  data[:table]
98
99
  end