This repository has been archived by the owner on Aug 29, 2018. It is now read-only.
/
frontend_proxy.rb
270 lines (227 loc) · 8.96 KB
/
frontend_proxy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#--
# Copyright 2013 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++
require 'rubygems'
require 'openshift-origin-node/utils/shell_exec'
require 'openshift-origin-node/utils/node_logger'
require 'openshift-origin-common'
require 'syslog'
require 'fileutils'
module OpenShift
module Runtime
class FrontendProxyServerException < StandardError
attr_reader :uid
def initialize(msg=nil, uid=nil)
@uid = uid
super(msg)
end
def to_s
m = super
m+= ": uid=#{@uid}" if not @uid.nil?
m
end
end
# == Frontend Proxy Server
#
# Represents the front-end proxy server on the system. Upon initialization,
# defaults necessary to compute ranges are extracted from Openshift::Config.
#
# Note: This is the HAProxy implementation; other implementations may vary.
class FrontendProxyServer
include NodeLogger
def initialize
# Extract config values and compute the port range this proxy
# instance should act upon, relative to the given container/user.
config = ::OpenShift::Config.new
@port_begin = (config.get("PORT_BEGIN") || "35531").to_i
@ports_per_user = (config.get("PORTS_PER_USER") || "5").to_i
@uid_begin = (config.get("UID_BEGIN") || "500").to_i
end
# Returns a Range representing the valid proxy port values for the
# given UID.
#
# The port proxy range is determined by configuration and must
# produce identical results to the abstract cartridge provided
# range.
#
# Note, due to a mismatch between dev and prod this is
# intentionally not GEAR_MIN_UID and the range must
# wrap back around on itself.
def port_range(uid)
proxy_port_begin = (uid - @uid_begin) % ((65536 - @port_begin) / @ports_per_user) * @ports_per_user + @port_begin
(proxy_port_begin ... (proxy_port_begin + @ports_per_user))
end
# Deletes an existing proxy mapping for the specified UID, IP and port.
#
# Returns nil on success or a FrontendProxyServerException on failure.
def delete(uid, ip, port)
raise "No UID specified" if uid == nil
raise "No IP specified" if ip == nil
raise "No port specified" if port == nil
raise "Invalid port specified" unless port.is_a? Integer
target_addr = "#{ip}:#{port}"
mapped_proxy_port = find_mapped_proxy_port(uid, ip, port)
# No existing mapping, nothing to do.
return if mapped_proxy_port == nil
out, err, rc = system_proxy_delete(mapped_proxy_port)
if rc != 0
raise FrontendProxyServerException.new(
"System proxy failed to delete #{mapped_proxy_port} => #{target_addr}(#{rc}): stdout: #{out} stderr: #{err}", uid)
end
end
# Deletes any existing proxy ports associated with the given UID.
#
# If ignore_errors is specified, any delete attempts will be logged
# and ignored. Otherwise, any exception will be immediately be re-raised.
#
# Returns nil on success.
def delete_all_for_uid(uid, ignore_errors=true)
raise "No UID specified" if uid == nil
proxy_ports = []
port_range(uid).each { |proxy_port| proxy_ports << proxy_port }
delete_all(proxy_ports, ignore_errors)
end
# Deletes all proxy ports in the specified array.
#
# If ignore_errors is specified, any delete attempts will be logged
# and ignored. Otherwise, any exception will be immediately be re-raised.
#
# Returns the exit code from the call to system_proxy_delete if no
# exception is raised.
def delete_all(proxy_ports, ignore_errors=true)
raise "No proxy ports specified" if proxy_ports == nil
out, err, rc = system_proxy_delete(*proxy_ports)
if (rc != 0)
message = "System proxy delete of port(s) #{proxy_ports} failed(#{rc}): stdout: #{out} stderr: #{err}"
if ignore_errors
logger.warn(message)
else
raise FrontendProxyServerException.new(message)
end
end
return rc
end
# Adds a new proxy mapping for the specified UID, IP and target port
# using the next available proxy port in the user's range.
#
# Returns an Integer value for the mapped proxy port on success. Raises a
# FrontendProxyServerException if the mapping attempt fails or if no ports
# are left available to map.
def add(uid, ip, port)
raise "No UID specified" if uid == nil
raise "No IP specified" if ip == nil
raise "No port specified" if port == nil
raise "Invalid port specified" unless port.is_a? Integer
requested_addr = "#{ip}:#{port}"
port_range(uid).each do |proxy_port|
# Get the existing mapped address for this proxy port
current_addr = system_proxy_show(proxy_port)
# If there's already a mapping for this proxy port, return
# the existing mapping if it matches the request, otherwise
# skip it as being already in use
if current_addr != nil
if current_addr == requested_addr
return proxy_port
else
next
end
end
# No existing mapping exists, so attempt to create one
out, err, rc = system_proxy_set({:proxy_port => proxy_port, :addr => requested_addr})
if rc != 0
raise FrontendProxyServerException.new(
"System proxy set for #{proxy_port}=>#{requested_addr} failed(#{rc}): stdout: #{out} stderr: #{err}", uid)
end
return proxy_port
end
raise FrontendProxyServerException.new("No ports were left available to map #{requested_addr}", uid)
end
# Find the proxy port for a given UID, IP and target port.
#
# Examples:
#
# find_mapped_proxy_port(500, '127.0.0.1', 8080)
# => 35531
#
# Returns the proxy port if found, otherwise nil.
def find_mapped_proxy_port(uid, ip, port)
raise "No UID specified" if uid == nil
raise "No IP specified" if ip == nil
raise "No port specified" if port == nil
raise "Invalid port specified" unless port.is_a? Integer
target_addr = "#{ip}:#{port}"
mapped_proxy_port = nil
port_range(uid).each do |proxy_port|
current_addr = system_proxy_show(proxy_port)
if current_addr == target_addr
mapped_proxy_port = proxy_port
break
end
end
return mapped_proxy_port
end
# System interface to delete one or more proxy entries.
#
# Example:
#
# system_proxy_delete(30000)
# => [out, err, rc]
#
# system_proxy_delete(30000, 30001, 30002)
# => [out, err, rc]
def system_proxy_delete(*ports)
if ports != nil && ports.length > 0
cmd = %{oo-iptables-port-proxy removeproxy}
ports.each { |port| cmd << " #{port}" }
out, err, rc = ::OpenShift::Runtime::Utils::oo_spawn(cmd)
return out, err, rc
else
return nil, nil, 0
end
end
# System interface to set a proxy entry. Expects a varargs
# array of hashes with keys :proxy_port and :addr representing
# a mapping entry.
#
# Example:
#
# system_proxy_set({:proxy_port => 30000, :addr => "127.0.0.1:8080"})
# => [out, err, rc]
#
# system_proxy_set(
# {:proxy_port => 30000, :addr => "127.0.0.1:8080"},
# {:proxy_port => 30001, :addr => "127.0.0.1:8081"}
# )
# => [out, err, rc]
def system_proxy_set(*mappings)
if mappings != nil && mappings.length > 0
cmd = %{oo-iptables-port-proxy addproxy}
mappings.each { |mapping| cmd << %Q{ #{mapping[:proxy_port]} "#{mapping[:addr]}"} }
out, err, rc = ::OpenShift::Runtime::Utils::oo_spawn(cmd)
return out, err, rc
else
return nil, nil, 0
end
end
# System interface to show an existing proxy entry.
def system_proxy_show(proxy_port)
raise "No proxy port specified" unless proxy_port != nil
target, err, rc = ::OpenShift::Runtime::Utils::oo_spawn(%{oo-iptables-port-proxy showproxy #{proxy_port} | awk '{ print $2 }'})
target.chomp!
return target.length > 0 ? target : nil
end
end
end
end