Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tools/capistrano/lib/capistrano/gateway.rb

Revision 7145, 3.7 kB (checked in by minam, 3 years ago)

Added support for :on_error => :continue in task definitions, allowing tasks to effectively ignore connection and execution errors that occur as they run

Line 
1 if RUBY_VERSION == "1.8.6"
2   begin
3     require 'fastthread'
4   rescue LoadError
5     warn "You are running Ruby 1.8.6, which has a bug in its threading implementation."
6     warn "You are liable to encounter deadlocks running Capistrano, unless you install"
7     warn "the fastthread library, which is available as a gem:"
8     warn "   gem install fastthread"
9   end
10 end
11
12 require 'thread'
13 require 'capistrano/errors'
14 require 'capistrano/ssh'
15 require 'capistrano/server_definition'
16
17 Thread.abort_on_exception = true
18
19 module Capistrano
20
21   # Black magic. It uses threads and Net::SSH to set up a connection to a
22   # gateway server, through which connections to other servers may be
23   # tunnelled.
24   #
25   # It is used internally by Capistrano, but may be useful on its own, as well.
26   #
27   # Usage:
28   #
29   #   gateway = Capistrano::Gateway.new(Capistrano::ServerDefinition.new('gateway.example.com'))
30   #
31   #   sess1 = gateway.connect_to(Capistrano::ServerDefinition.new('hidden.example.com'))
32   #   sess2 = gateway.connect_to(Capistrano::ServerDefinition.new('other.example.com'))
33   class Gateway
34     # The Thread instance driving the gateway connection.
35     attr_reader :thread
36
37     # The Net::SSH session representing the gateway connection.
38     attr_reader :session
39
40     MAX_PORT = 65535
41     MIN_PORT = 1024
42
43     def initialize(server, options={}) #:nodoc:
44       @options = options
45       @next_port = MAX_PORT
46       @terminate_thread = false
47       @port_guard = Mutex.new
48
49       mutex = Mutex.new
50       waiter = ConditionVariable.new
51
52       mutex.synchronize do
53         @thread = Thread.new do
54           logger.trace "starting connection to gateway `#{server}'" if logger
55           SSH.connect(server, @options) do |@session|
56             logger.trace "gateway connection established" if logger
57             mutex.synchronize { waiter.signal }
58             @session.loop do
59               !@terminate_thread
60             end
61           end
62         end
63
64         waiter.wait(mutex)
65       end
66     end
67
68     # Shuts down all forwarded connections and terminates the gateway.
69     def shutdown!
70       # cancel all active forward channels
71       session.forward.active_locals.each do |lport, host, port|
72         session.forward.cancel_local(lport)
73       end
74
75       # terminate the gateway thread
76       @terminate_thread = true
77
78       # wait for the gateway thread to stop
79       thread.join
80     end
81
82     # Connects to the given server by opening a forwarded port from the local
83     # host to the server, via the gateway, and then opens and returns a new
84     # Net::SSH connection via that port.
85     def connect_to(server)
86       connection = nil
87       logger.debug "establishing connection to `#{server}' via gateway" if logger
88       local_port = next_port
89
90       thread = Thread.new do
91         begin
92           local_host = ServerDefinition.new("127.0.0.1", :user => server.user, :port => local_port)
93           session.forward.local(local_port, server.host, server.port || 22)
94           connection = SSH.connect(local_host, @options)
95           connection.xserver = server
96           logger.trace "connected: `#{server}' (via gateway)" if logger
97         rescue Errno::EADDRINUSE
98           local_port = next_port
99           retry
100         rescue Exception => e
101           warn "#{e.class}: #{e.message}"
102           warn e.backtrace.join("\n")
103         end
104       end
105
106       thread.join
107       if connection.nil?
108         error = ConnectionError.new("could not establish connection to `#{server}'")
109         error.hosts = [server]
110         raise error
111       end
112    
113       connection
114     end
115
116     private
117
118       def logger
119         @options[:logger]
120       end
121
122       def next_port
123         @port_guard.synchronize do
124           port = @next_port
125           @next_port -= 1
126           @next_port = MAX_PORT if @next_port < MIN_PORT
127           port
128         end
129       end
130   end
131 end
Note: See TracBrowser for help on using the browser.