class CFTunnel
Constants
- HELPER_APP
- HELPER_NAME
- HELPER_VERSION
bump this AND the version info reported by HELPER_APP/server.rb this is to keep the helper in sync with any updates here
- PORT_RANGE
- TUNNEL_CHECK_LIMIT
Public Class Methods
new(client, service, port = 10000)
click to toggle source
# File lib/tunnel/tunnel.rb, line 15 def initialize(client, service, port = 10000) @client = client @service = service @port = port end
Public Instance Methods
open!()
click to toggle source
# File lib/tunnel/tunnel.rb, line 21 def open! if helper auth = helper_auth unless helper_healthy?(auth) delete_helper auth = create_helper end else auth = create_helper end bind_to_helper if @service && !helper_already_binds? info = get_connection_info(auth) start_tunnel(info, auth) info end
pick_port!(port = @port)
click to toggle source
# File lib/tunnel/tunnel.rb, line 64 def pick_port!(port = @port) original = port PORT_RANGE.times do |n| begin TCPSocket.open("localhost", port) port += 1 rescue return @port = port end end @port = grab_ephemeral_port end
wait_for_end()
click to toggle source
# File lib/tunnel/tunnel.rb, line 55 def wait_for_end if @local_tunnel_thread @local_tunnel_thread.join else raise "Tunnel wasn't started!" end end
wait_for_start()
click to toggle source
# File lib/tunnel/tunnel.rb, line 42 def wait_for_start 10.times do |n| begin TCPSocket.open("localhost", @port).close return true rescue => e sleep 1 end end raise "Could not connect to local tunnel." end
Private Instance Methods
bind_to_helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 174 def bind_to_helper helper.bind(@service) helper.restart! end
create_helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 85 def create_helper auth = UUIDTools::UUID.random_create.to_s push_helper(auth) start_helper auth end
delete_helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 148 def delete_helper helper.delete! invalidate_tunnel_app_info end
get_connection_info(token)
click to toggle source
# File lib/tunnel/tunnel.rb, line 206 def get_connection_info(token) response = nil 10.times do begin response = RestClient.get( helper_url + "/" + safe_path("services", @service.name), "Auth-Token" => token) break rescue RestClient::Exception => e sleep 1 end end unless response raise "Remote tunnel helper is unaware of #{@service.name}!" end is_v2 = @client.is_a?(CFoundry::V2::Client) info = JSON.parse(response) case (is_v2 ? @service.service_plan.service.label : @service.vendor) when "rabbitmq" uri = Addressable::URI.parse info["url"] info["hostname"] = uri.host info["port"] = uri.port info["vhost"] = uri.path[1..-1] info["user"] = uri.user info["password"] = uri.password info.delete "url" # we use "db" as the "name" for mongo # existing "name" is junk when "mongodb" info["name"] = info["db"] info.delete "db" # our "name" is irrelevant for redis when "redis" info.delete "name" when "filesystem" raise "Tunneling is not supported for this type of service" end ["hostname", "port", "password"].each do |k| raise "Could not determine #{k} for #{@service.name}" if info[k].nil? end info end
grab_ephemeral_port()
click to toggle source
# File lib/tunnel/tunnel.rb, line 287 def grab_ephemeral_port socket = TCPServer.new("0.0.0.0", 0) socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) Socket.do_not_reverse_lookup = true socket.addr[1] ensure socket.close end
helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 81 def helper @helper ||= @client.app_by_name(HELPER_NAME) end
helper_already_binds?()
click to toggle source
# File lib/tunnel/tunnel.rb, line 118 def helper_already_binds? helper.binds? @service end
helper_auth()
click to toggle source
# File lib/tunnel/tunnel.rb, line 92 def helper_auth helper.env["CALDECOTT_AUTH"] end
helper_healthy?(token)
click to toggle source
# File lib/tunnel/tunnel.rb, line 96 def helper_healthy?(token) return false unless helper.healthy? begin response = RestClient.get( "#{helper_url}/info", "Auth-Token" => token ) info = JSON.parse(response) if info["version"] == HELPER_VERSION true else stop_helper false end rescue RestClient::Exception stop_helper false end end
helper_url()
click to toggle source
# File lib/tunnel/tunnel.rb, line 184 def helper_url return @helper_url if @helper_url tun_url = helper.url ["https", "http"].each do |scheme| url = "#{scheme}://#{tun_url}" begin RestClient.get(url) # https failed rescue Errno::ECONNREFUSED # we expect a 404 since this request isn't auth'd rescue RestClient::ResourceNotFound return @helper_url = url end end raise "Cannot determine URL for #{tun_url}" end
invalidate_tunnel_app_info()
click to toggle source
# File lib/tunnel/tunnel.rb, line 179 def invalidate_tunnel_app_info @helper_url = nil @helper = nil end
push_helper(token)
click to toggle source
# File lib/tunnel/tunnel.rb, line 122 def push_helper(token) app = @client.app app.name = HELPER_NAME app.command = "bundle exec ruby server.rb" app.total_instances = 1 app.memory = 128 app.env = {"CALDECOTT_AUTH" => token} space = app.space = @client.current_space app.create! app.bind(@service) if @service domain = @client.domains.find { |d| d.owning_organization == nil } app.create_route(:domain => domain, :space => space, :host => random_helper_url) begin app.upload(HELPER_APP) invalidate_tunnel_app_info rescue app.delete! raise end end
random_helper_url()
click to toggle source
# File lib/tunnel/tunnel.rb, line 276 def random_helper_url random = sprintf("%x", rand(1000000)) "caldecott-#{random}" end
safe_path(*segments)
click to toggle source
# File lib/tunnel/tunnel.rb, line 281 def safe_path(*segments) segments.flatten.collect { |x| URI.encode x.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") }.join("/") end
start_helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 159 def start_helper helper.start! seconds = 0 until helper.healthy? sleep 1 seconds += 1 if seconds == TUNNEL_CHECK_LIMIT raise "Helper application failed to start." end end invalidate_tunnel_app_info end
start_tunnel(conn_info, auth)
click to toggle source
# File lib/tunnel/tunnel.rb, line 259 def start_tunnel(conn_info, auth) @local_tunnel_thread = Thread.new do Caldecott::Client.start({ :local_port => @port, :tun_url => helper_url, :dst_host => conn_info["hostname"], :dst_port => conn_info["port"], :log_file => STDOUT, :log_level => ENV["CF_TUNNEL_DEBUG"] || "ERROR", :auth_token => auth, :quiet => true }) end at_exit { @local_tunnel_thread.kill } end
stop_helper()
click to toggle source
# File lib/tunnel/tunnel.rb, line 153 def stop_helper helper.stop! invalidate_tunnel_app_info end