ec2-api-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,58 @@
1
+ = ec2-api-proxy
2
+
3
+ == Description
4
+
5
+ ec2-api-proxy is a proxy server for EC2 API.
6
+
7
+ == Source Code
8
+
9
+ https://bitbucket.org/winebarrel/ec2-api-proxy
10
+
11
+ == Dependency
12
+
13
+ * EventMachine
14
+ * RExec
15
+ * Dalli
16
+
17
+ == Install
18
+
19
+ gem install ec2-api-proxy
20
+
21
+ == Example
22
+
23
+ shell> ec2-api-proxy start
24
+ Starting daemon...
25
+ Waiting for daemon to start...
26
+ Daemon status: running pid=56921
27
+
28
+ shell> eapctl status
29
+ ---
30
+ memcached: localhost:11211
31
+ expires: 60
32
+ compress: false
33
+ debug: false
34
+ shell> eapctl set-debug true
35
+
36
+ shell> export HTTP_PROXY=localhost:8080
37
+ shell> aws ec2 describe-instances --endpoint-url=http://ec2.ap-northeast-1.amazonaws.com # It does not support HTTPS still
38
+ {
39
+ "Reservations": [
40
+ {
41
+ ...
42
+ shell> aws ec2 describe-instances --endpoint-url=http://ec2.ap-northeast-1.amazonaws.com
43
+ {
44
+ "Reservations": [
45
+ {
46
+ ...
47
+
48
+ shell> tail -f /var/log/ec2-api-proxy.log
49
+ ...
50
+ D, [2013-07-15T20:41:28.472712 #76304] DEBUG -- : proxy: connect from 127.0.0.1:58456
51
+ D, [2013-07-15T20:41:28.529715 #76304] DEBUG -- : proxy: cache miss
52
+ D, [2013-07-15T20:41:29.051745 #76304] DEBUG -- : proxy: unbind connection from 127.0.0.1:58456
53
+ D, [2013-07-15T20:41:29.051745 #76304] DEBUG -- : backend: unbind connection
54
+ D, [2013-07-15T20:41:29.052745 #76304] DEBUG -- : backend: set cache (length=22330)
55
+ D, [2013-07-15T20:41:31.202868 #76304] DEBUG -- : proxy: connect from 127.0.0.1:58459
56
+ D, [2013-07-15T20:41:31.207869 #76304] DEBUG -- : proxy: cache hit (length=22330)
57
+ D, [2013-07-15T20:41:31.209869 #76304] DEBUG -- : proxy: unbind connection from 127.0.0.1:58459
58
+
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'drb/drb'
5
+ require 'optparse'
6
+ require 'ec2-api-proxy/constants'
7
+ require 'yaml'
8
+
9
+ COMMAND_NAME = File.basename(__FILE__)
10
+
11
+ COMMANDS = {
12
+ 'status' => lambda {|server_options|
13
+ h = {}
14
+ server_options.to_hash.each {|k, v| h[k.to_s] = v }
15
+ puts YAML.dump(h)
16
+ },
17
+ 'set-expires' => lambda {|server_options, v|
18
+ server_options[:expires] = v.to_i
19
+ },
20
+ 'set-debug' => lambda {|server_options, v|
21
+ server_options[:debug] = !!(/#{Regexp.escape(v)}/i =~ 'true')
22
+ },
23
+ }
24
+
25
+ options = nil
26
+
27
+ ARGV.options do |parser|
28
+ cmds = COMMANDS.keys
29
+
30
+ options = {
31
+ :socket => "/var/tmp/#{APP_NAME}.sock",
32
+ }
33
+
34
+ parser.on('-S', '--socket=SOCK_FILE') {|v| options[:socket] = v }
35
+
36
+ help_and_exit = lambda do |v|
37
+ $stderr.puts parser.help.sub(COMMAND_NAME, "#{COMMAND_NAME} {#{cmds.join('|')}}")
38
+ exit 1
39
+ end
40
+
41
+ parser.on('-h', '--help', &help_and_exit)
42
+
43
+ parser.parse!
44
+
45
+ unless ARGV.length >= 1 and cmds.include?(ARGV[0])
46
+ help_and_exit.call(true)
47
+ end
48
+ end # parse options
49
+
50
+ server_options = DRbObject.new_with_uri("drbunix:#{options[:socket]}")
51
+
52
+ cmd = COMMANDS.fetch(ARGV[0])
53
+
54
+ if cmd.arity > 1
55
+ cmd.call(server_options, ARGV[1])
56
+ else
57
+ cmd.call(server_options)
58
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'optparse'
5
+ require 'ec2-api-proxy/constants'
6
+ require 'ec2-api-proxy/server'
7
+
8
+ options = nil
9
+
10
+ ARGV.options do |parser|
11
+ daemon_commands = %w(start stop restart status)
12
+
13
+ options = {
14
+ :addr => '0.0.0.0',
15
+ :port => 8080,
16
+ :memcached => 'localhost:11211',
17
+ :expires => 60,
18
+ :compress => false,
19
+ :working_dir => '/var',
20
+ :socket => "/var/tmp/#{APP_NAME}.sock",
21
+ :debug => false,
22
+ }
23
+
24
+ parser.on('-a', '--addr=ADDR' ) {|v| options[:addr] = v }
25
+ parser.on('-p', '--port=PORT' ) {|v| options[:port] = v.to_i }
26
+ parser.on('-m', '--memcached=HOST:PORT') {|v| options[:memcached] = v }
27
+ parser.on('-e', '--expires=SECOND' ) {|v| options[:expires] = v.to_i }
28
+ parser.on('-c', '--compress' ) { options[:compress] = true }
29
+ parser.on('-n', '--threads=SIZE' ) {|v| options[:threads] = v.to_i }
30
+ parser.on('-W', '--working-dir=DIR' ) {|v| options[:working_dir] = v }
31
+ parser.on('-S', '--socket=SOCK_FILE' ) {|v| options[:socket] = v }
32
+ parser.on('', '--debug' ) { options[:debug] = true }
33
+
34
+ help_and_exit = lambda do |v|
35
+ $stderr.puts parser.help.sub(APP_NAME, "#{APP_NAME} {#{daemon_commands.join('|')}}")
36
+ exit 1
37
+ end
38
+
39
+ parser.on('-h', '--help', &help_and_exit)
40
+
41
+ parser.parse!
42
+
43
+ unless ARGV.length == 1 and daemon_commands.include?(ARGV[0])
44
+ help_and_exit.call(true)
45
+ end
46
+ end # parse options
47
+
48
+ EC2APIProxy::Server.options = options
49
+ EC2APIProxy::Server.daemonize
@@ -0,0 +1,46 @@
1
+ require 'eventmachine'
2
+
3
+ module EC2APIProxy
4
+ class Backend < EM::Connection
5
+ def initialize(proxy, memcached_key)
6
+ @proxy = proxy
7
+ @logger = proxy.logger
8
+ @memcached_key = memcached_key
9
+ @data_list = []
10
+ end
11
+
12
+ def unbind
13
+ if @proxy and @proxy.options[:debug]
14
+ @logger.debug("backend: unbind connection")
15
+ end
16
+
17
+ unless @data_list.empty?
18
+ data = @data_list.join
19
+ @proxy.memcached.set(@memcached_key, data, @proxy.options[:expires])
20
+
21
+ if @proxy and @proxy.options[:debug]
22
+ @logger.debug("backend: set cache (length=#{data.length})")
23
+ end
24
+ end
25
+ end
26
+
27
+ def receive_data(data)
28
+ if not @proxy or @proxy.error?
29
+ @logger.info("backend: proxy connection error: #{data.inspect}")
30
+ close_proxy_connection
31
+ close_connection_after_writing
32
+ else
33
+ @data_list << data
34
+ @proxy.send_data(data)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def close_proxy_connection
41
+ @proxy.close_connection if @proxy
42
+ rescue Exception => e
43
+ @logger.warn("#{e.class.name}: #{e.message}")
44
+ end
45
+ end # Backend
46
+ end # EC2APIProxy
@@ -0,0 +1,2 @@
1
+ APP_NAME = 'ec2-api-proxy'
2
+ Version = '0.1.0'
@@ -0,0 +1,129 @@
1
+ require 'eventmachine'
2
+ require 'ec2-api-proxy/backend'
3
+ require 'logger'
4
+ require 'uri'
5
+ require 'cgi'
6
+
7
+ module EC2APIProxy
8
+ class Proxy < EM::Connection
9
+ attr_reader :options
10
+ attr_reader :logger
11
+ attr_reader :memcached
12
+
13
+ def initialize(options)
14
+ @options = options
15
+ @memcached = Dalli::Client.new(options[:memcached], :compress => options[:compress])
16
+ @logger = Logger.new($stdout)
17
+ end
18
+
19
+ def post_init
20
+ if (peername = get_peername)
21
+ @connect_from = Socket.unpack_sockaddr_in(peername)
22
+ end
23
+
24
+ if @options[:debug]
25
+ port, ip = @connect_from
26
+ @logger.debug("proxy: connect from #{ip}:#{port}")
27
+ end
28
+ end
29
+
30
+ def unbind
31
+ close_backend_connection
32
+
33
+ if @options[:debug]
34
+ port, ip = @connect_from
35
+ @logger.debug("proxy: unbind connection from #{ip}:#{port}")
36
+ end
37
+ end
38
+
39
+ def receive_data(data)
40
+ EM.defer {
41
+ begin
42
+ endpoint, params = parse_proxy_request(data)
43
+ key = params_to_key(params)
44
+
45
+ if (cache = @memcached.get(key))
46
+ @logger.debug("proxy: cache hit (length=#{cache.length})") if @options[:debug]
47
+ send_data(cache)
48
+ close_connection_after_writing
49
+ else
50
+ @logger.debug("proxy: cache miss") if @options[:debug]
51
+ @backend = EM.connect(endpoint.host, endpoint.port, EC2APIProxy::Backend, self, key)
52
+
53
+ if @backend.error?
54
+ @logger.error("proxy: backend connection error: #{data.inspect}")
55
+ send_error('ec2-api-proxy-backend-connection-error', 'backend connection error')
56
+ close_backend_connection
57
+ close_connection_after_writing
58
+ else
59
+ @backend.send_data(data)
60
+ end
61
+ end
62
+ rescue => e
63
+ @logger.warn("#{e.class.name}: #{e.message}")
64
+ send_error(e.class.name, e.message)
65
+ close_backend_connection
66
+ close_connection_after_writing
67
+ end
68
+ }
69
+ end
70
+
71
+ private
72
+
73
+ def parse_proxy_request(data)
74
+ request_method = nil
75
+ endpoint = nil
76
+
77
+ data.sub!(%r{\A(GET|POST) (https?://[^/]+)}) do
78
+ request_method = $1
79
+ endpoint = $2
80
+ "#{request_method} "
81
+ end
82
+
83
+ endpoint = URI.parse(endpoint)
84
+ params = nil
85
+
86
+ if request_method =~ /GET/i
87
+ params = CGI.parse(endpoint.query)
88
+ else
89
+ params = CGI.parse(data.split(/\r\n\r\n/, 2).last)
90
+ end
91
+
92
+ [endpoint, params]
93
+ end
94
+
95
+ def params_to_key(params)
96
+ params = params.dup
97
+
98
+ %w(
99
+ SignatureVersion
100
+ AWSAccessKeyId
101
+ Timestamp
102
+ SignatureMethod
103
+ Version
104
+ Signature
105
+ ).each {|i| params.delete(i) }
106
+
107
+ params.sort.map {|k, v| "#{k}=#{v}" }.join('&')
108
+ end
109
+
110
+ def send_error(code, message)
111
+ send_data(<<-EOS)
112
+ <Response>
113
+ <Errors>
114
+ <Error>
115
+ <Code>#{code}</Code>
116
+ <Message>#{message}</Message>
117
+ </Error>
118
+ </Errors>
119
+ </Response>
120
+ EOS
121
+ end
122
+
123
+ def close_backend_connection
124
+ @backend.close_connection if @backend
125
+ rescue Exception => e
126
+ @logger.warn("#{e.class.name}: #{e.message}")
127
+ end
128
+ end # Proxy
129
+ end # EC2APIProxy
@@ -0,0 +1,48 @@
1
+ require 'drb/drb'
2
+ require 'fileutils'
3
+ require 'eventmachine'
4
+ require 'rexec'
5
+ require 'rexec/daemon'
6
+ require 'dalli'
7
+
8
+ require 'ec2-api-proxy/proxy'
9
+
10
+ module RExec; module Daemon; class Base
11
+ def self.daemon_name; APP_NAME; end
12
+ end; end; end
13
+
14
+ module EC2APIProxy
15
+ class Server < RExec::Daemon::Base
16
+
17
+ class << self
18
+ def options=(options)
19
+ @@options = options
20
+ @@base_directory = options[:working_dir]
21
+ end
22
+
23
+ def run
24
+ @@control_options = {
25
+ :memcached => @@options[:memcached],
26
+ :expires => @@options[:expires],
27
+ :compress => @@options[:compress],
28
+ :debug => @@options[:debug],
29
+ }
30
+
31
+ # start DRb
32
+ FileUtils.rm_f(@@options[:socket])
33
+ DRb.start_service("drbunix:#{@@options[:socket]}", @@control_options)
34
+ File.chmod(0700, @@options[:socket])
35
+ at_exit { FileUtils.rm_f(@@options[:socket]) }
36
+
37
+ EM.epoll
38
+ EM.threadpool_size = @@options[:threads] if @@options[:threads]
39
+
40
+ EM.run {
41
+ EM.start_server(@@options[:addr], @@options[:port], EC2APIProxy::Proxy,
42
+ @@control_options)
43
+ }
44
+ end
45
+ end # self
46
+
47
+ end # Server
48
+ end # EC2APIProxy
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ec2-api-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - winebarrel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.3
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: 1.0.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: rexec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.1
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: 1.5.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: dalli
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.6.4
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: 2.6.4
62
+ description:
63
+ email: sgwr_dts@yahoo.co.jp
64
+ executables:
65
+ - ec2-api-proxy
66
+ - eapctl
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - README
71
+ - bin/eapctl
72
+ - bin/ec2-api-proxy
73
+ - lib/ec2-api-proxy/backend.rb
74
+ - lib/ec2-api-proxy/constants.rb
75
+ - lib/ec2-api-proxy/proxy.rb
76
+ - lib/ec2-api-proxy/server.rb
77
+ homepage: https://bitbucket.org/winebarrel/ec2-api-proxy
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.23
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: ec2-api-proxy is a proxy server for EC2 API.
101
+ test_files: []