bosh_aws_registry 0.2.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/README +3 -0
- data/Rakefile +50 -0
- data/bin/aws_registry +39 -0
- data/bin/migrate +42 -0
- data/lib/aws_registry/api_controller.rb +66 -0
- data/lib/aws_registry/config.rb +84 -0
- data/lib/aws_registry/errors.rb +20 -0
- data/lib/aws_registry/instance_manager.rb +73 -0
- data/lib/aws_registry/models/aws_instance.rb +13 -0
- data/lib/aws_registry/models.rb +11 -0
- data/lib/aws_registry/runner.rb +56 -0
- data/lib/aws_registry/version.rb +7 -0
- data/lib/aws_registry/yaml_helper.rb +24 -0
- data/lib/aws_registry.rb +26 -0
- data/spec/assets/sample_config.yml +17 -0
- data/spec/spec_helper.rb +113 -0
- data/spec/unit/api_controller_spec.rb +74 -0
- data/spec/unit/config_spec.rb +56 -0
- data/spec/unit/instance_manager_spec.rb +92 -0
- data/spec/unit/runner_spec.rb +84 -0
- metadata +157 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../../rake", __FILE__))
|
4
|
+
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler"
|
9
|
+
Bundler.setup(:default, :test)
|
10
|
+
|
11
|
+
require "rake"
|
12
|
+
begin
|
13
|
+
require "rspec/core/rake_task"
|
14
|
+
rescue LoadError
|
15
|
+
end
|
16
|
+
|
17
|
+
require "bundler_task"
|
18
|
+
require "ci_task"
|
19
|
+
|
20
|
+
gem_helper = Bundler::GemHelper.new(Dir.pwd)
|
21
|
+
|
22
|
+
desc "Build AWS Registry gem into the pkg directory"
|
23
|
+
task "build" do
|
24
|
+
gem_helper.build_gem
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Build and install AWS Registry into system gems"
|
28
|
+
task "install" do
|
29
|
+
Rake::Task["bundler:install"].invoke
|
30
|
+
gem_helper.install_gem
|
31
|
+
end
|
32
|
+
|
33
|
+
BundlerTask.new
|
34
|
+
|
35
|
+
if defined?(RSpec)
|
36
|
+
namespace :spec do
|
37
|
+
desc "Run Unit Tests"
|
38
|
+
rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
|
39
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
40
|
+
t.rspec_opts = %w(--format progress --colour)
|
41
|
+
end
|
42
|
+
|
43
|
+
CiTask.new do |task|
|
44
|
+
task.rspec_task = rspec_task
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Run tests"
|
49
|
+
task :spec => %w(spec:unit)
|
50
|
+
end
|
data/bin/aws_registry
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
gemfile = File.expand_path("../../Gemfile", __FILE__)
|
4
|
+
|
5
|
+
if File.exists?(gemfile)
|
6
|
+
ENV["BUNDLE_GEMFILE"] = gemfile
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler/setup"
|
9
|
+
end
|
10
|
+
|
11
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
12
|
+
|
13
|
+
require "aws_registry"
|
14
|
+
require "optparse"
|
15
|
+
|
16
|
+
config_file = nil
|
17
|
+
|
18
|
+
opts = OptionParser.new do |opts|
|
19
|
+
opts.on("-c", "--config FILE", "configuration file") do |opt|
|
20
|
+
config_file = opt
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.parse!(ARGV.dup)
|
25
|
+
|
26
|
+
if config_file.nil?
|
27
|
+
puts opts
|
28
|
+
exit 1
|
29
|
+
end
|
30
|
+
|
31
|
+
runner = Bosh::AwsRegistry::Runner.new(config_file)
|
32
|
+
|
33
|
+
Signal.trap("INT") do
|
34
|
+
runner.stop
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
runner.run
|
39
|
+
|
data/bin/migrate
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require "bundler/setup"
|
7
|
+
require "logger"
|
8
|
+
require "sequel"
|
9
|
+
|
10
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
11
|
+
|
12
|
+
require "aws_registry"
|
13
|
+
|
14
|
+
config_file = nil
|
15
|
+
|
16
|
+
opts = OptionParser.new do |opts|
|
17
|
+
opts.on("-c", "--config FILE", "configuration file") do |opt|
|
18
|
+
config_file = opt
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.parse!(ARGV.dup)
|
23
|
+
|
24
|
+
if config_file.nil?
|
25
|
+
puts opts
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
include Bosh::AwsRegistry::YamlHelper
|
30
|
+
|
31
|
+
config = load_yaml_file(config_file)
|
32
|
+
|
33
|
+
db = Bosh::AwsRegistry.connect_db(config["db"])
|
34
|
+
migrations_dir = File.expand_path("../../db/migrations", __FILE__)
|
35
|
+
|
36
|
+
options = {
|
37
|
+
:table => "aws_registry_schema"
|
38
|
+
}
|
39
|
+
|
40
|
+
Sequel.extension :migration
|
41
|
+
Sequel::TimestampMigrator.run(db, migrations_dir, options)
|
42
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
|
5
|
+
class ApiController < Sinatra::Base
|
6
|
+
|
7
|
+
not_found do
|
8
|
+
json(:status => "not_found")
|
9
|
+
end
|
10
|
+
|
11
|
+
error do
|
12
|
+
exception = request.env["sinatra.error"]
|
13
|
+
@logger.error(exception)
|
14
|
+
status(500)
|
15
|
+
json(:status => "error")
|
16
|
+
end
|
17
|
+
|
18
|
+
get "/instances/:instance_id/settings" do
|
19
|
+
ip_check = authorized? ? nil : request.ip
|
20
|
+
settings = @instance_manager.read_settings(params[:instance_id], ip_check)
|
21
|
+
json(:status => "ok", :settings => settings)
|
22
|
+
end
|
23
|
+
|
24
|
+
put "/instances/:instance_id/settings" do
|
25
|
+
protected!
|
26
|
+
@instance_manager.update_settings(params[:instance_id], request.body.read)
|
27
|
+
json(:status => "ok")
|
28
|
+
end
|
29
|
+
|
30
|
+
delete "/instances/:instance_id/settings" do
|
31
|
+
protected!
|
32
|
+
@instance_manager.delete_settings(params[:instance_id])
|
33
|
+
json(:status => "ok")
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
super
|
38
|
+
@logger = Bosh::AwsRegistry.logger
|
39
|
+
|
40
|
+
@users = Set.new
|
41
|
+
@users << [Bosh::AwsRegistry.http_user, Bosh::AwsRegistry.http_password]
|
42
|
+
@instance_manager = InstanceManager.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def protected!
|
46
|
+
unless authorized?
|
47
|
+
headers("WWW-Authenticate" => 'Basic realm="EC2 Registry"')
|
48
|
+
halt(401, json("status" => "access_denied"))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def authorized?
|
53
|
+
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
54
|
+
@auth.provided? &&
|
55
|
+
@auth.basic? &&
|
56
|
+
@auth.credentials &&
|
57
|
+
@users.include?(@auth.credentials)
|
58
|
+
end
|
59
|
+
|
60
|
+
def json(payload)
|
61
|
+
Yajl::Encoder.encode(payload)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
AWS_MAX_RETRIES = 2
|
8
|
+
AWS_EC2_ENDPOINT = "ec2.amazonaws.com"
|
9
|
+
|
10
|
+
attr_accessor :logger
|
11
|
+
attr_accessor :http_port
|
12
|
+
attr_accessor :http_user
|
13
|
+
attr_accessor :http_password
|
14
|
+
attr_accessor :db
|
15
|
+
|
16
|
+
attr_writer :ec2
|
17
|
+
|
18
|
+
def configure(config)
|
19
|
+
validate_config(config)
|
20
|
+
|
21
|
+
@logger ||= Logger.new(config["logfile"] || STDOUT)
|
22
|
+
|
23
|
+
if config["loglevel"].kind_of?(String)
|
24
|
+
@logger.level = Logger.const_get(config["loglevel"].upcase)
|
25
|
+
end
|
26
|
+
|
27
|
+
@http_port = config["http"]["port"]
|
28
|
+
@http_user = config["http"]["user"]
|
29
|
+
@http_password = config["http"]["password"]
|
30
|
+
|
31
|
+
@aws = config["aws"]
|
32
|
+
|
33
|
+
@aws_options = {
|
34
|
+
:access_key_id => @aws["access_key_id"],
|
35
|
+
:secret_access_key => @aws["secret_access_key"],
|
36
|
+
:max_retries => @aws["max_retries"] || AWS_MAX_RETRIES,
|
37
|
+
:ec2_endpoint => @aws["ec2_endpoint"] || AWS_EC2_ENDPOINT,
|
38
|
+
:logger => @logger
|
39
|
+
}
|
40
|
+
|
41
|
+
@db = connect_db(config["db"])
|
42
|
+
end
|
43
|
+
|
44
|
+
def ec2
|
45
|
+
@ec2 ||= AWS::EC2.new(@aws_options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def connect_db(db_config)
|
49
|
+
connection_options = {
|
50
|
+
:max_connections => db_config["max_connections"],
|
51
|
+
:pool_timeout => db_config["pool_timeout"]
|
52
|
+
}
|
53
|
+
|
54
|
+
db = Sequel.connect(db_config["database"], connection_options)
|
55
|
+
db.logger = @logger
|
56
|
+
db.sql_log_level = :debug
|
57
|
+
db
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_config(config)
|
61
|
+
unless config.is_a?(Hash)
|
62
|
+
raise ConfigError, "Invalid config format, Hash expected, " \
|
63
|
+
"#{config.class} given"
|
64
|
+
end
|
65
|
+
|
66
|
+
unless config.has_key?("http") && config["http"].is_a?(Hash)
|
67
|
+
raise ConfigError, "HTTP configuration is missing from " \
|
68
|
+
"config file"
|
69
|
+
end
|
70
|
+
|
71
|
+
unless config.has_key?("db") && config["db"].is_a?(Hash)
|
72
|
+
raise ConfigError, "Database configuration is missing from " \
|
73
|
+
"config file"
|
74
|
+
end
|
75
|
+
|
76
|
+
unless config.has_key?("aws") && config["aws"].is_a?(Hash)
|
77
|
+
raise ConfigError, "AWS configuration is missing from " \
|
78
|
+
"config file"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
|
5
|
+
class Error < StandardError
|
6
|
+
def self.code(code = 500)
|
7
|
+
define_method(:code) { code }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FatalError < Error; end
|
12
|
+
|
13
|
+
class ConfigError < Error; end
|
14
|
+
class ConnectionError < Error; end
|
15
|
+
|
16
|
+
class AwsError < Error; end
|
17
|
+
|
18
|
+
class InstanceError < Error; end
|
19
|
+
class InstanceNotFound < Error; code(404); end
|
20
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
|
5
|
+
class InstanceManager
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@logger = Bosh::AwsRegistry.logger
|
9
|
+
@ec2 = Bosh::AwsRegistry.ec2
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Updates instance settings
|
14
|
+
# @param [String] instance_id EC2 instance id (instance record
|
15
|
+
# will be created in DB if it doesn't already exist)
|
16
|
+
# @param [String] settings New settings for the instance
|
17
|
+
def update_settings(instance_id, settings)
|
18
|
+
params = {
|
19
|
+
:instance_id => instance_id
|
20
|
+
}
|
21
|
+
|
22
|
+
instance = Models::AwsInstance[params] || Models::AwsInstance.new(params)
|
23
|
+
instance.settings = settings
|
24
|
+
instance.save
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Reads instance settings
|
29
|
+
# @param [String] instance_id EC2 instance id
|
30
|
+
# @param [optional, String] remote_ip If this IP is provided,
|
31
|
+
# check will be performed to see if it instance id
|
32
|
+
# actually has this IP address according to EC2.
|
33
|
+
def read_settings(instance_id, remote_ip = nil)
|
34
|
+
check_instance_ip(remote_ip, instance_id) if remote_ip
|
35
|
+
|
36
|
+
get_instance(instance_id).settings
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_settings(instance_id)
|
40
|
+
get_instance(instance_id).destroy
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def check_instance_ip(ip, instance_id)
|
46
|
+
return if ip == "127.0.0.1"
|
47
|
+
actual_ip = instance_private_ip(instance_id)
|
48
|
+
unless ip == actual_ip
|
49
|
+
raise InstanceError, "Instance IP mismatch, expected IP is " \
|
50
|
+
"`%s', actual IP is `%s'" % [ ip, actual_ip ]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_instance(instance_id)
|
55
|
+
instance = Models::AwsInstance[:instance_id => instance_id]
|
56
|
+
|
57
|
+
if instance.nil?
|
58
|
+
raise InstanceNotFound, "Can't find instance `#{instance_id}'"
|
59
|
+
end
|
60
|
+
|
61
|
+
instance
|
62
|
+
end
|
63
|
+
|
64
|
+
def instance_private_ip(instance_id)
|
65
|
+
@ec2.instances[instance_id].private_ip_address
|
66
|
+
rescue AWS::Errors::Base => e
|
67
|
+
raise Bosh::AwsRegistry::AwsError, "AWS error: #{e}"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
class Runner
|
5
|
+
include YamlHelper
|
6
|
+
|
7
|
+
def initialize(config_file)
|
8
|
+
Bosh::AwsRegistry.configure(load_yaml_file(config_file))
|
9
|
+
|
10
|
+
@logger = Bosh::AwsRegistry.logger
|
11
|
+
@http_port = Bosh::AwsRegistry.http_port
|
12
|
+
@http_user = Bosh::AwsRegistry.http_user
|
13
|
+
@http_password = Bosh::AwsRegistry.http_password
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
@logger.info("BOSH AWS Registry starting...")
|
18
|
+
EM.kqueue if EM.kqueue?
|
19
|
+
EM.epoll if EM.epoll?
|
20
|
+
|
21
|
+
EM.error_handler { |e| handle_em_error(e) }
|
22
|
+
|
23
|
+
EM.run do
|
24
|
+
start_http_server
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
@logger.info("BOSH AWS Registry shutting down...")
|
30
|
+
@http_server.stop! if @http_server
|
31
|
+
EM.stop
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_http_server
|
35
|
+
@logger.info "HTTP server is starting on port #{@http_port}..."
|
36
|
+
@http_server = Thin::Server.new("0.0.0.0", @http_port, :signals => false) do
|
37
|
+
Thin::Logging.silent = true
|
38
|
+
map "/" do
|
39
|
+
run Bosh::AwsRegistry::ApiController.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@http_server.start!
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def handle_em_error(e)
|
48
|
+
@logger.send(level, e.to_s)
|
49
|
+
if e.respond_to?(:backtrace) && e.backtrace.respond_to?(:join)
|
50
|
+
@logger.send(level, e.backtrace.join("\n"))
|
51
|
+
end
|
52
|
+
stop
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::AwsRegistry
|
4
|
+
module YamlHelper
|
5
|
+
|
6
|
+
def load_yaml_file(path, expected_type = Hash)
|
7
|
+
unless File.exists?(path)
|
8
|
+
raise(ConfigError, "Cannot find file `#{path}'")
|
9
|
+
end
|
10
|
+
|
11
|
+
yaml = YAML.load_file(path)
|
12
|
+
|
13
|
+
if expected_type && !yaml.is_a?(expected_type)
|
14
|
+
raise ConfigError, "Incorrect file format in `#{path}', " \
|
15
|
+
"#{expected_type} expected"
|
16
|
+
end
|
17
|
+
|
18
|
+
yaml
|
19
|
+
rescue SystemCallError => e
|
20
|
+
raise ConfigError, "Cannot load YAML file at `#{path}': #{e}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/aws_registry.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh
|
4
|
+
module AwsRegistry
|
5
|
+
autoload :Models, "aws_registry/models"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "aws-sdk"
|
10
|
+
require "logger"
|
11
|
+
require "sequel"
|
12
|
+
require "sinatra/base"
|
13
|
+
require "thin"
|
14
|
+
require "yajl"
|
15
|
+
|
16
|
+
require "aws_registry/yaml_helper"
|
17
|
+
|
18
|
+
require "aws_registry/api_controller"
|
19
|
+
require "aws_registry/config"
|
20
|
+
require "aws_registry/errors"
|
21
|
+
require "aws_registry/instance_manager"
|
22
|
+
require "aws_registry/runner"
|
23
|
+
require "aws_registry/version"
|
24
|
+
|
25
|
+
Sequel::Model.plugin :validation_helpers
|
26
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
4
|
+
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "bundler"
|
9
|
+
Bundler.setup(:default, :test)
|
10
|
+
|
11
|
+
require "fileutils"
|
12
|
+
require "logger"
|
13
|
+
require "tmpdir"
|
14
|
+
|
15
|
+
require "rspec"
|
16
|
+
require "rack/test"
|
17
|
+
|
18
|
+
module SpecHelper
|
19
|
+
class << self
|
20
|
+
attr_accessor :logger
|
21
|
+
attr_accessor :temp_dir
|
22
|
+
|
23
|
+
def init
|
24
|
+
ENV["RACK_ENV"] = "test"
|
25
|
+
configure_logging
|
26
|
+
configure_temp_dir
|
27
|
+
|
28
|
+
require "aws_registry"
|
29
|
+
init_database
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure_logging
|
33
|
+
if ENV["DEBUG"]
|
34
|
+
@logger = Logger.new(STDOUT)
|
35
|
+
else
|
36
|
+
path = File.expand_path("../spec.log", __FILE__)
|
37
|
+
log_file = File.open(path, "w")
|
38
|
+
log_file.sync = true
|
39
|
+
@logger = Logger.new(log_file)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure_temp_dir
|
44
|
+
@temp_dir = Dir.mktmpdir
|
45
|
+
ENV["TMPDIR"] = @temp_dir
|
46
|
+
FileUtils.mkdir_p(@temp_dir)
|
47
|
+
at_exit { FileUtils.rm_rf(@temp_dir) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def init_database
|
51
|
+
@migrations_dir = File.expand_path("../../db/migrations", __FILE__)
|
52
|
+
|
53
|
+
Sequel.extension :migration
|
54
|
+
|
55
|
+
@db = Sequel.sqlite(:database => nil, :max_connections => 32, :pool_timeout => 10)
|
56
|
+
@db.loggers << @logger
|
57
|
+
Bosh::AwsRegistry.db = @db
|
58
|
+
|
59
|
+
run_migrations
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_migrations
|
63
|
+
Sequel::Migrator.apply(@db, @migrations_dir, nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_database
|
67
|
+
@db.execute("PRAGMA foreign_keys = OFF")
|
68
|
+
@db.tables.each do |table|
|
69
|
+
@db.drop_table(table)
|
70
|
+
end
|
71
|
+
@db.execute("PRAGMA foreign_keys = ON")
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset
|
75
|
+
reset_database
|
76
|
+
run_migrations
|
77
|
+
|
78
|
+
Bosh::AwsRegistry.db = @db
|
79
|
+
Bosh::AwsRegistry.logger = @logger
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
SpecHelper.init
|
85
|
+
|
86
|
+
def valid_config
|
87
|
+
{
|
88
|
+
"logfile" => nil,
|
89
|
+
"loglevel" => "debug",
|
90
|
+
"http" => {
|
91
|
+
"user" => "admin",
|
92
|
+
"password" => "admin",
|
93
|
+
"port" => 25777
|
94
|
+
},
|
95
|
+
"db" => {
|
96
|
+
"max_connections" => 433,
|
97
|
+
"pool_timeout" => 227,
|
98
|
+
"database" => "sqlite:///:memory:"
|
99
|
+
},
|
100
|
+
"aws" => {
|
101
|
+
"access_key_id" => "foo",
|
102
|
+
"secret_access_key" => "bar",
|
103
|
+
"max_retries" => 5
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
RSpec.configure do |rspec|
|
109
|
+
rspec.before(:each) do
|
110
|
+
SpecHelper.reset
|
111
|
+
Bosh::AwsRegistry.logger = Logger.new(StringIO.new)
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
4
|
+
|
5
|
+
describe Bosh::AwsRegistry::ApiController do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
Bosh::AwsRegistry.http_user = "admin"
|
9
|
+
Bosh::AwsRegistry.http_password = "admin"
|
10
|
+
|
11
|
+
@instance_manager = mock("instance manager")
|
12
|
+
Bosh::AwsRegistry::InstanceManager.stub!(:new).and_return(@instance_manager)
|
13
|
+
|
14
|
+
rack_mock = Rack::MockSession.new(Bosh::AwsRegistry::ApiController.new)
|
15
|
+
@session = Rack::Test::Session.new(rack_mock)
|
16
|
+
end
|
17
|
+
|
18
|
+
def expect_json_response(response, status, body)
|
19
|
+
response.status.should == status
|
20
|
+
Yajl::Parser.parse(response.body).should == body
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns settings for given EC2 instance (IP check)" do
|
24
|
+
@instance_manager.should_receive(:read_settings).
|
25
|
+
with("foo", "127.0.0.1").and_return("bar")
|
26
|
+
|
27
|
+
@session.get("/instances/foo/settings")
|
28
|
+
|
29
|
+
expect_json_response(@session.last_response, 200,
|
30
|
+
{ "status" => "ok", "settings" => "bar" })
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns settings (authorized user, no IP check)" do
|
34
|
+
@instance_manager.should_receive(:read_settings).
|
35
|
+
with("foo", nil).and_return("bar")
|
36
|
+
|
37
|
+
@session.basic_authorize("admin", "admin")
|
38
|
+
@session.get("/instances/foo/settings")
|
39
|
+
|
40
|
+
expect_json_response(@session.last_response, 200,
|
41
|
+
{ "status" => "ok", "settings" => "bar" })
|
42
|
+
end
|
43
|
+
|
44
|
+
it "updates settings" do
|
45
|
+
@session.put("/instances/foo/settings", {}, { :input => "bar" })
|
46
|
+
expect_json_response(@session.last_response, 401,
|
47
|
+
{ "status" => "access_denied" })
|
48
|
+
|
49
|
+
@instance_manager.should_receive(:update_settings).
|
50
|
+
with("foo", "bar").and_return(true)
|
51
|
+
|
52
|
+
@session.basic_authorize("admin", "admin")
|
53
|
+
@session.put("/instances/foo/settings", {}, { :input => "bar" })
|
54
|
+
|
55
|
+
expect_json_response(@session.last_response, 200,
|
56
|
+
{ "status" => "ok" })
|
57
|
+
end
|
58
|
+
|
59
|
+
it "deletes settings" do
|
60
|
+
@session.delete("/instances/foo/settings")
|
61
|
+
expect_json_response(@session.last_response, 401,
|
62
|
+
{ "status" => "access_denied" })
|
63
|
+
|
64
|
+
@instance_manager.should_receive(:delete_settings).
|
65
|
+
with("foo").and_return(true)
|
66
|
+
|
67
|
+
@session.basic_authorize("admin", "admin")
|
68
|
+
@session.delete("/instances/foo/settings")
|
69
|
+
|
70
|
+
expect_json_response(@session.last_response, 200,
|
71
|
+
{ "status" => "ok" })
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
4
|
+
|
5
|
+
describe Bosh::AwsRegistry do
|
6
|
+
|
7
|
+
describe "configuring AWS registry" do
|
8
|
+
it "reads provided configuration file and sets singletons" do
|
9
|
+
Bosh::AwsRegistry.configure(valid_config)
|
10
|
+
|
11
|
+
logger = Bosh::AwsRegistry.logger
|
12
|
+
|
13
|
+
logger.should be_kind_of(Logger)
|
14
|
+
logger.level.should == Logger::DEBUG
|
15
|
+
|
16
|
+
Bosh::AwsRegistry.http_port.should == 25777
|
17
|
+
Bosh::AwsRegistry.http_user.should == "admin"
|
18
|
+
Bosh::AwsRegistry.http_password.should == "admin"
|
19
|
+
|
20
|
+
db = Bosh::AwsRegistry.db
|
21
|
+
db.should be_kind_of(Sequel::SQLite::Database)
|
22
|
+
db.opts[:database].should == "/:memory:"
|
23
|
+
db.opts[:max_connections].should == 433
|
24
|
+
db.opts[:pool_timeout].should == 227
|
25
|
+
end
|
26
|
+
|
27
|
+
it "validates configuration file" do
|
28
|
+
expect {
|
29
|
+
Bosh::AwsRegistry.configure("foobar")
|
30
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError,
|
31
|
+
/Invalid config format/)
|
32
|
+
|
33
|
+
config = valid_config.merge("http" => nil)
|
34
|
+
|
35
|
+
expect {
|
36
|
+
Bosh::AwsRegistry.configure(config)
|
37
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError,
|
38
|
+
/HTTP configuration is missing/)
|
39
|
+
|
40
|
+
config = valid_config.merge("db" => nil)
|
41
|
+
|
42
|
+
expect {
|
43
|
+
Bosh::AwsRegistry.configure(config)
|
44
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError,
|
45
|
+
/Database configuration is missing/)
|
46
|
+
|
47
|
+
config = valid_config.merge("aws" => nil)
|
48
|
+
|
49
|
+
expect {
|
50
|
+
Bosh::AwsRegistry.configure(config)
|
51
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError,
|
52
|
+
/AWS configuration is missing/)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
4
|
+
|
5
|
+
describe Bosh::AwsRegistry::InstanceManager do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@ec2 = mock("ec2")
|
9
|
+
Bosh::AwsRegistry.ec2 = @ec2
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:manager) do
|
13
|
+
Bosh::AwsRegistry::InstanceManager.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_instance(params)
|
17
|
+
Bosh::AwsRegistry::Models::AwsInstance.create(params)
|
18
|
+
end
|
19
|
+
|
20
|
+
def actual_ip_is(ip)
|
21
|
+
instances = mock("instances")
|
22
|
+
instance = mock("instance")
|
23
|
+
@ec2.should_receive(:instances).and_return(instances)
|
24
|
+
instances.should_receive(:[]).with("foo").and_return(instance)
|
25
|
+
instance.should_receive(:private_ip_address).and_return(ip)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "reading settings" do
|
29
|
+
it "returns settings after verifying IP address" do
|
30
|
+
create_instance(:instance_id => "foo", :settings => "bar")
|
31
|
+
actual_ip_is("10.0.0.1")
|
32
|
+
manager.read_settings("foo", "10.0.0.1").should == "bar"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "raises an error if IP cannot be verified" do
|
36
|
+
create_instance(:instance_id => "foo", :settings => "bar")
|
37
|
+
actual_ip_is("10.0.0.2")
|
38
|
+
|
39
|
+
expect {
|
40
|
+
manager.read_settings("foo", "10.0.0.1")
|
41
|
+
}.to raise_error(Bosh::AwsRegistry::InstanceError,
|
42
|
+
"Instance IP mismatch, expected IP is `10.0.0.1', " \
|
43
|
+
"actual IP is `10.0.0.2'")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "doesn't check remote IP if it's not provided" do
|
47
|
+
create_instance(:instance_id => "foo", :settings => "bar")
|
48
|
+
manager.read_settings("foo").should == "bar"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raises an error if instance not found" do
|
52
|
+
expect {
|
53
|
+
manager.read_settings("foo")
|
54
|
+
}.to raise_error(Bosh::AwsRegistry::InstanceNotFound,
|
55
|
+
"Can't find instance `foo'")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "updating settings" do
|
60
|
+
it "updates settings (new instance)" do
|
61
|
+
manager.update_settings("foo", "baz")
|
62
|
+
manager.read_settings("foo").should == "baz"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "updates settings (existing instance)" do
|
66
|
+
create_instance(:instance_id => "foo", :settings => "bar")
|
67
|
+
manager.read_settings("foo").should == "bar"
|
68
|
+
manager.update_settings("foo", "baz")
|
69
|
+
manager.read_settings("foo").should == "baz"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "deleting settings" do
|
74
|
+
it "deletes settings" do
|
75
|
+
manager.update_settings("foo", "baz")
|
76
|
+
manager.delete_settings("foo")
|
77
|
+
|
78
|
+
expect {
|
79
|
+
manager.read_settings("foo")
|
80
|
+
}.to raise_error(Bosh::AwsRegistry::InstanceNotFound,
|
81
|
+
"Can't find instance `foo'")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "raises an error if instance not found" do
|
85
|
+
expect {
|
86
|
+
manager.delete_settings("foo")
|
87
|
+
}.to raise_error(Bosh::AwsRegistry::InstanceNotFound,
|
88
|
+
"Can't find instance `foo'")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
4
|
+
|
5
|
+
describe Bosh::AwsRegistry::Runner do
|
6
|
+
|
7
|
+
def make_runner(config_file)
|
8
|
+
Bosh::AwsRegistry::Runner.new(config_file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write_config(file, config)
|
12
|
+
File.open(file, "w") do |f|
|
13
|
+
YAML.dump(config, f)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "initializing" do
|
18
|
+
it "configures AWS registry using provided file" do
|
19
|
+
config_file = Tempfile.new("config")
|
20
|
+
config = { "key" => "value" }
|
21
|
+
write_config(config_file.path, config)
|
22
|
+
|
23
|
+
Bosh::AwsRegistry.should_receive(:configure).with(config)
|
24
|
+
make_runner(config_file.path)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "fails if config file not found" do
|
28
|
+
expect {
|
29
|
+
make_runner("foo")
|
30
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError, "Cannot find file `foo'")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "fails when config file has incorrect format" do
|
34
|
+
config_file = Tempfile.new("config")
|
35
|
+
config = "foo"
|
36
|
+
write_config(config_file.path, config)
|
37
|
+
|
38
|
+
expect {
|
39
|
+
make_runner(config_file.path)
|
40
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError, /Incorrect file format/)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "fails when some syscall fails" do
|
44
|
+
config_file = Tempfile.new("config")
|
45
|
+
write_config(config_file.path, { "foo" => "bar" })
|
46
|
+
|
47
|
+
YAML.stub!(:load_file).and_raise(SystemCallError.new("baz"))
|
48
|
+
|
49
|
+
expect {
|
50
|
+
make_runner(config_file.path)
|
51
|
+
}.to raise_error(Bosh::AwsRegistry::ConfigError, /baz/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "running/stopping" do
|
56
|
+
before(:each) do
|
57
|
+
@config_file = Tempfile.new("config")
|
58
|
+
write_config(@config_file.path, valid_config)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "spins up/shuts down reactor and HTTP server" do
|
62
|
+
runner = make_runner(@config_file)
|
63
|
+
mock_thin = mock("thin")
|
64
|
+
|
65
|
+
EM.should_receive(:run).and_yield
|
66
|
+
|
67
|
+
Thin::Server.should_receive(:new).
|
68
|
+
with("0.0.0.0", 25777, :signals => false).
|
69
|
+
and_return(mock_thin)
|
70
|
+
|
71
|
+
mock_thin.should_receive(:start!)
|
72
|
+
|
73
|
+
runner.run
|
74
|
+
|
75
|
+
mock_thin.should_receive(:stop!)
|
76
|
+
EM.should_receive(:stop)
|
77
|
+
|
78
|
+
runner.stop
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bosh_aws_registry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- VMware
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sequel
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sinatra
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: thin
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: yajl-ruby
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: aws-sdk
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.3.6
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.3.6
|
94
|
+
description: BOSH AWS registry
|
95
|
+
email: support@vmware.com
|
96
|
+
executables:
|
97
|
+
- aws_registry
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- bin/aws_registry
|
102
|
+
- bin/migrate
|
103
|
+
- lib/aws_registry.rb
|
104
|
+
- lib/aws_registry/api_controller.rb
|
105
|
+
- lib/aws_registry/config.rb
|
106
|
+
- lib/aws_registry/errors.rb
|
107
|
+
- lib/aws_registry/instance_manager.rb
|
108
|
+
- lib/aws_registry/models.rb
|
109
|
+
- lib/aws_registry/models/aws_instance.rb
|
110
|
+
- lib/aws_registry/runner.rb
|
111
|
+
- lib/aws_registry/version.rb
|
112
|
+
- lib/aws_registry/yaml_helper.rb
|
113
|
+
- README
|
114
|
+
- Rakefile
|
115
|
+
- spec/assets/sample_config.yml
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/unit/api_controller_spec.rb
|
118
|
+
- spec/unit/config_spec.rb
|
119
|
+
- spec/unit/instance_manager_spec.rb
|
120
|
+
- spec/unit/runner_spec.rb
|
121
|
+
homepage: http://www.vmware.com
|
122
|
+
licenses: []
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
hash: -3082160961986360018
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
hash: -3082160961986360018
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 1.8.24
|
148
|
+
signing_key:
|
149
|
+
specification_version: 3
|
150
|
+
summary: BOSH AWS registry
|
151
|
+
test_files:
|
152
|
+
- spec/assets/sample_config.yml
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
- spec/unit/api_controller_spec.rb
|
155
|
+
- spec/unit/config_spec.rb
|
156
|
+
- spec/unit/instance_manager_spec.rb
|
157
|
+
- spec/unit/runner_spec.rb
|