dynamo-autoscale 0.3.6 → 0.4.1

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