openshift-origin-node 1.3.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.
Potentially problematic release.
This version of openshift-origin-node might be problematic. Click here for more details.
- data/COPYRIGHT +1 -0
- data/Gemfile +4 -0
- data/LICENSE +11 -0
- data/README.md +3 -0
- data/Rakefile +28 -0
- data/bin/oo-add-alias +93 -0
- data/bin/oo-app-create +110 -0
- data/bin/oo-app-destroy +100 -0
- data/bin/oo-app-state-show +74 -0
- data/bin/oo-authorized-ssh-key-add +83 -0
- data/bin/oo-authorized-ssh-key-remove +82 -0
- data/bin/oo-broker-auth-key-add +84 -0
- data/bin/oo-broker-auth-key-remove +72 -0
- data/bin/oo-cartridge-info +70 -0
- data/bin/oo-cartridge-list +70 -0
- data/bin/oo-connector-execute +94 -0
- data/bin/oo-env-var-add +81 -0
- data/bin/oo-env-var-remove +78 -0
- data/bin/oo-get-quota +64 -0
- data/bin/oo-remove-alias +93 -0
- data/bin/oo-set-quota +59 -0
- data/conf/node.conf +30 -0
- data/conf/resource_limits.template +67 -0
- data/lib/openshift-origin-node.rb +29 -0
- data/lib/openshift-origin-node/config.rb +21 -0
- data/lib/openshift-origin-node/environment.rb +26 -0
- data/lib/openshift-origin-node/model/application_container.rb +298 -0
- data/lib/openshift-origin-node/model/frontend_httpd.rb +346 -0
- data/lib/openshift-origin-node/model/node.rb +134 -0
- data/lib/openshift-origin-node/model/unix_user.rb +738 -0
- data/lib/openshift-origin-node/plugins/unix_user_observer.rb +86 -0
- data/lib/openshift-origin-node/utils/shell_exec.rb +115 -0
- data/lib/openshift-origin-node/version.rb +23 -0
- data/misc/bin/oo-admin-ctl-cgroups +482 -0
- data/misc/bin/oo-cgroup-read +25 -0
- data/misc/bin/oo-get-mcs-level +29 -0
- data/misc/bin/oo-trap-user +248 -0
- data/misc/bin/rhcsh +155 -0
- data/misc/bin/setup_pam_fs_limits.sh +146 -0
- data/misc/bin/teardown_pam_fs_limits.sh +73 -0
- data/misc/doc/cgconfig.conf +26 -0
- data/misc/etc/openshift-run.conf +1 -0
- data/misc/init/openshift-cgroups +56 -0
- data/misc/services/openshift-cgroups.service +14 -0
- data/openshift-origin-node.gemspec +31 -0
- data/rubygem-openshift-origin-node.spec +263 -0
- data/test/test_helper.rb +20 -0
- data/test/unit/frontend_httpd_test.rb +144 -0
- data/test/unit/unix_user_test.rb +95 -0
- data/test/unit/version_test.rb +45 -0
- metadata +230 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
|
20
|
+
require 'fileutils'
|
21
|
+
require 'getoptlong'
|
22
|
+
require 'json'
|
23
|
+
require 'parseconfig'
|
24
|
+
require "openshift-origin-common"
|
25
|
+
require "openshift-origin-node/version"
|
26
|
+
require "openshift-origin-node/environment"
|
27
|
+
require "openshift-origin-node/model/application_container"
|
28
|
+
require "openshift-origin-node/model/node"
|
29
|
+
require "openshift-origin-node/model/frontend_httpd"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# openshift-origin-node/config has been moved to openshift-origin-common/config.
|
2
|
+
# This stub remains for code in li that still requires the former. Once
|
3
|
+
# everything has migrated to openshift-common/config, this file can be
|
4
|
+
# deleted. -- Miciah, 2012-10-02
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
|
8
|
+
require 'openshift-origin-common'
|
9
|
+
|
10
|
+
module OpenShift
|
11
|
+
class Config
|
12
|
+
# This is a bit of a hack. The old OpenShift::Config was a singleton
|
13
|
+
# object, and so users would use OpenShift::Config.instance to get it.
|
14
|
+
# Here, we define .instance to return a new instance. Hopefully, nothing
|
15
|
+
# is relying on the standard singleton behavior whereby .instance always
|
16
|
+
# returns the same instance.
|
17
|
+
def self.instance
|
18
|
+
OpenShift::Config.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'openshift-origin-common'
|
19
|
+
|
20
|
+
module OpenShift
|
21
|
+
#load OPENSHIFT_NODE_PLUGINS
|
22
|
+
plugin_list = Config.new.get('OPENSHIFT_NODE_PLUGINS').split(',')
|
23
|
+
plugin_list.each do |plugin|
|
24
|
+
require "#{plugin}" unless plugin.start_with?('#')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'openshift-origin-node/model/unix_user'
|
19
|
+
require 'openshift-origin-node/utils/shell_exec'
|
20
|
+
require 'openshift-origin-common'
|
21
|
+
require 'logger'
|
22
|
+
|
23
|
+
module OpenShift
|
24
|
+
# == Application Container
|
25
|
+
class ApplicationContainer < Model
|
26
|
+
include OpenShift::Utils::ShellExec
|
27
|
+
attr_reader :uuid, :application_uuid, :user
|
28
|
+
|
29
|
+
# Represents all possible application states.
|
30
|
+
module State
|
31
|
+
BUILDING = "building"
|
32
|
+
DEPLOYING = "deploying"
|
33
|
+
IDLE = "idle"
|
34
|
+
NEW = "new"
|
35
|
+
STARTED = "started"
|
36
|
+
STOPPED = "stopped"
|
37
|
+
UNKNOWN = "unknown"
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(application_uuid, container_uuid, user_uid = nil,
|
41
|
+
app_name = nil, container_name = nil, namespace = nil, quota_blocks = nil, quota_files = nil, logger = nil)
|
42
|
+
@logger = logger ||= Logger.new(STDOUT)
|
43
|
+
|
44
|
+
@config = OpenShift::Config.new
|
45
|
+
|
46
|
+
@uuid = container_uuid
|
47
|
+
@application_uuid = application_uuid
|
48
|
+
@user = UnixUser.new(application_uuid, container_uuid, user_uid,
|
49
|
+
app_name, container_name, namespace, quota_blocks, quota_files)
|
50
|
+
end
|
51
|
+
|
52
|
+
def name
|
53
|
+
@uuid
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create gear - model/unix_user.rb
|
57
|
+
def create
|
58
|
+
notify_observers(:before_container_create)
|
59
|
+
@user.create
|
60
|
+
notify_observers(:after_container_create)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Destroy gear - model/unix_user.rb
|
64
|
+
def destroy(skip_hooks=false)
|
65
|
+
notify_observers(:before_container_destroy)
|
66
|
+
|
67
|
+
hook_timeout=30
|
68
|
+
|
69
|
+
output = ""
|
70
|
+
errout = ""
|
71
|
+
retcode = 0
|
72
|
+
|
73
|
+
hooks={}
|
74
|
+
["pre", "post"].each do |hooktype|
|
75
|
+
if @user.homedir.nil? || ! File.exists?(@user.homedir)
|
76
|
+
hooks[hooktype]=[]
|
77
|
+
else
|
78
|
+
hooks[hooktype] = Dir.entries(@user.homedir).map { |cart|
|
79
|
+
[ File.join(@config.get("CARTRIDGE_BASE_PATH"),cart,"info","hooks","#{hooktype}-destroy"),
|
80
|
+
File.join(@config.get("CARTRIDGE_BASE_PATH"),"embedded",cart,"info","hooks","#{hooktype}-destroy"),
|
81
|
+
].select { |hook| File.exists? hook }[0]
|
82
|
+
}.select { |hook|
|
83
|
+
not hook.nil?
|
84
|
+
}.map { |hook|
|
85
|
+
"#{hook} #{@user.container_name} #{@user.namespace} #{@user.container_uuid}"
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
unless skip_hooks
|
91
|
+
hooks["pre"].each do | cmd |
|
92
|
+
out,err,rc = shellCmd(cmd, "/", true, 0, hook_timeout)
|
93
|
+
errout << err if not err.nil?
|
94
|
+
output << out if not out.nil?
|
95
|
+
retcode = 121 if rc != 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@user.destroy
|
100
|
+
|
101
|
+
unless skip_hooks
|
102
|
+
hooks["post"].each do | cmd |
|
103
|
+
out,err,rc = shellCmd(cmd, "/", true, 0, hook_timeout)
|
104
|
+
errout << err if not err.nil?
|
105
|
+
output << out if not out.nil?
|
106
|
+
retcode = 121 if rc != 0
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
notify_observers(:after_container_destroy)
|
111
|
+
|
112
|
+
return output, errout, retcode
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Fetch application state from gear.
|
116
|
+
# Returns app state as string on Success and 'unknown' on Failure
|
117
|
+
def get_app_state
|
118
|
+
env = load_env
|
119
|
+
app_state_file=File.join(env[:OPENSHIFT_HOMEDIR], 'app-root', 'runtime', '.state')
|
120
|
+
|
121
|
+
if File.exists?(app_state_file)
|
122
|
+
app_state = nil
|
123
|
+
File.open(app_state_file) { |input| app_state = input.read.chomp }
|
124
|
+
else
|
125
|
+
app_state = :UNKNOWN
|
126
|
+
end
|
127
|
+
app_state
|
128
|
+
end
|
129
|
+
|
130
|
+
# Public: Sets the application state.
|
131
|
+
#
|
132
|
+
# new_state - The new state to assign. Must be an ApplicationContainer::State.
|
133
|
+
def set_app_state(new_state)
|
134
|
+
new_state_val = nil
|
135
|
+
begin
|
136
|
+
new_state_val = State.const_get(new_state)
|
137
|
+
rescue
|
138
|
+
raise ArgumentError, "Invalid state '#{new_state}' specified"
|
139
|
+
end
|
140
|
+
|
141
|
+
env = load_env
|
142
|
+
app_state_file = File.join(env[:OPENSHIFT_HOMEDIR], 'app-root', 'runtime', '.state')
|
143
|
+
|
144
|
+
raise "Couldn't find app state file at #{app_state_file}" unless File.exists?(app_state_file)
|
145
|
+
|
146
|
+
File.open(app_state_file, File::WRONLY|File::TRUNC|File::CREAT, 0o0660) {|file|
|
147
|
+
file.write "#{new_state_val}\n"
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
# Public: Sets the app state to "stopped" and causes an immediate forced
|
152
|
+
# termination of all gear processes.
|
153
|
+
#
|
154
|
+
# TODO: exception handling
|
155
|
+
def force_stop
|
156
|
+
set_app_state(:STOPPED)
|
157
|
+
UnixUser.kill_procs(@user.uid)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Public: Cleans up the gear, providing any installed
|
162
|
+
# cartridges with the opportinity to perform their own
|
163
|
+
# cleanup operations via the tidy hook.
|
164
|
+
#
|
165
|
+
# The generic gear-level cleanup flow is:
|
166
|
+
# * Stop the gear
|
167
|
+
# * Git cleanup
|
168
|
+
# * Gear temp dir cleanup
|
169
|
+
# * Cartridge tidy hook executions
|
170
|
+
# * Start the gear
|
171
|
+
#
|
172
|
+
# Raises an Exception if an internal error occurs, and ignores
|
173
|
+
# failed cartridge tidy hook executions.
|
174
|
+
def tidy
|
175
|
+
@logger.debug("Starting tidy on gear #{@uuid}")
|
176
|
+
|
177
|
+
env = load_env
|
178
|
+
gear_dir = env[:OPENSHIFT_HOMEDIR]
|
179
|
+
app_name = env[:OPENSHIFT_APP_NAME]
|
180
|
+
gear_repo_dir = File.join(gear_dir, 'git', "#{app_name}.git")
|
181
|
+
gear_tmp_dir = File.join(gear_dir, '.tmp')
|
182
|
+
|
183
|
+
begin
|
184
|
+
# Stop the gear. If this fails, consider the tidy a failure.
|
185
|
+
out, err, rc = shellCmd("/usr/sbin/oo-admin-ctl-gears stopgear #{@user.uuid}", gear_dir, false, 0)
|
186
|
+
@logger.debug("Stopped gear #{@uuid}. Output:\n#{out}")
|
187
|
+
rescue OpenShift::Utils::ShellExecutionException => e
|
188
|
+
@logger.error(%Q{
|
189
|
+
Couldn't stop gear #{@uuid} for tidy: #{e.message}
|
190
|
+
--- stdout ---\n#{e.stdout}
|
191
|
+
--- stderr ---\n#{e.stderr}
|
192
|
+
})
|
193
|
+
raise "Tidy failed on gear #{@uuid}; the gear couldn't be stopped successfully"
|
194
|
+
end
|
195
|
+
|
196
|
+
# Perform the individual tidy actions. At this point, the gear has been stopped,
|
197
|
+
# and so we'll attempt to start the gear no matter what tidy operations fail.
|
198
|
+
begin
|
199
|
+
# Git pruning
|
200
|
+
tidy_action do
|
201
|
+
out, err, rc = shellCmd("git prune", gear_repo_dir, false, 0)
|
202
|
+
@logger.debug("Pruned git directory at #{gear_repo_dir}. Output:\n#{out}")
|
203
|
+
end
|
204
|
+
|
205
|
+
# Git GC
|
206
|
+
tidy_action do
|
207
|
+
out, err, rc = shellCmd("git gc --aggressive", gear_repo_dir, false, 0)
|
208
|
+
@logger.debug("Executed git gc for repo #{gear_repo_dir}. Output:\n#{out}")
|
209
|
+
end
|
210
|
+
|
211
|
+
# Temp dir cleanup
|
212
|
+
tidy_action do
|
213
|
+
FileUtils.rm_rf(Dir.glob(File.join(gear_tmp_dir, "*")))
|
214
|
+
@logger.debug("Cleaned gear temp dir at #{gear_tmp_dir}")
|
215
|
+
end
|
216
|
+
|
217
|
+
# Execute the tidy hooks in any installed carts, in the context
|
218
|
+
# of the gear user. For now, we detect cart installations by iterating
|
219
|
+
# over the gear subdirs and using the dir names to construct a path
|
220
|
+
# to cart scripts in the base cartridge directory. If such a file exists,
|
221
|
+
# it's assumed the cart is installed on the gear.
|
222
|
+
cart_tidy_timeout = 30
|
223
|
+
Dir.entries(gear_dir).each do |gear_subdir|
|
224
|
+
tidy_script = File.join(@config.get("CARTRIDGE_BASE_PATH"), gear_subdir, "info", "hooks", "tidy")
|
225
|
+
|
226
|
+
next unless File.exists?(tidy_script)
|
227
|
+
|
228
|
+
begin
|
229
|
+
# Execute the hook in the context of the gear user
|
230
|
+
@logger.debug("Executing cart tidy script #{tidy_script} in gear #{@uuid} as user #{@user.uid}:#{@user.gid}")
|
231
|
+
OpenShift::Utils::ShellExec.run_as(@user.uid, @user.gid, tidy_script, gear_dir, false, 0, cart_tidy_timeout)
|
232
|
+
rescue OpenShift::Utils::ShellExecutionException => e
|
233
|
+
@logger.warn("Cartridge tidy operation failed on gear #{@uuid} for cart #{gear_dir}: #{e.message} (rc=#{e.rc})")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
rescue Exception => e
|
237
|
+
@logger.warn("An unknown exception occured during tidy for gear #{@uuid}: #{e.message}\n#{e.backtrace}")
|
238
|
+
ensure
|
239
|
+
begin
|
240
|
+
# Start the gear, and if that fails raise an exception, as the app is now
|
241
|
+
# in a bad state.
|
242
|
+
out, err, rc = shellCmd("/usr/sbin/oo-admin-ctl-gears startgear #{@user.uuid}", gear_dir)
|
243
|
+
@logger.debug("Started gear #{@uuid}. Output:\n#{out}")
|
244
|
+
rescue OpenShift::Utils::ShellExecutionException => e
|
245
|
+
@logger.error(%Q{
|
246
|
+
Failed to restart gear #{@uuid} following tidy: #{e.message}
|
247
|
+
--- stdout ---\n#{e.stdout}
|
248
|
+
--- stderr ---\n#{e.stderr}
|
249
|
+
})
|
250
|
+
raise "Tidy of gear #{@uuid} failed, and the gear was not successfuly restarted"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
@logger.debug("Completed tidy for gear #{@uuid}")
|
255
|
+
end
|
256
|
+
|
257
|
+
# Executes a block, trapping ShellExecutionExceptions and treating them
|
258
|
+
# as warnings. Any other exceptions are unexpected and will bubble out.
|
259
|
+
def tidy_action
|
260
|
+
begin
|
261
|
+
yield
|
262
|
+
rescue OpenShift::Utils::ShellExecutionException => e
|
263
|
+
@logger.warn(%Q{
|
264
|
+
Tidy operation failed on gear #{@uuid}: #{e.message}
|
265
|
+
--- stdout ---\n#{e.stdout}
|
266
|
+
--- stderr ---\n#{e.stderr}
|
267
|
+
})
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Public: Load a gears environment variables into the environment
|
272
|
+
#
|
273
|
+
# Examples
|
274
|
+
#
|
275
|
+
# load_env
|
276
|
+
# # => {"OPENSHIFT_APP_NAME"=>"myapp"}
|
277
|
+
#
|
278
|
+
# Returns env Array
|
279
|
+
def load_env
|
280
|
+
env = {}
|
281
|
+
# Load environment variables into a hash
|
282
|
+
|
283
|
+
Dir["#{user.homedir}/.env/*"].each { | f |
|
284
|
+
next if File.directory?(f)
|
285
|
+
contents = nil
|
286
|
+
File.open(f) {|input|
|
287
|
+
contents = input.read.chomp
|
288
|
+
index = contents.index('=')
|
289
|
+
contents = contents[(index + 1)..-1]
|
290
|
+
contents = contents[/'(.*)'/, 1] if contents.start_with?("'")
|
291
|
+
contents = contents[/"(.*)"/, 1] if contents.start_with?('"')
|
292
|
+
}
|
293
|
+
env[File.basename(f).intern] = contents
|
294
|
+
}
|
295
|
+
env
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2010 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'openshift-origin-node/utils/shell_exec'
|
19
|
+
require 'openshift-origin-common'
|
20
|
+
require 'syslog'
|
21
|
+
require 'fileutils'
|
22
|
+
|
23
|
+
module OpenShift
|
24
|
+
|
25
|
+
class FrontendHttpServerException < StandardError
|
26
|
+
attr_reader :container_uuid, :container_name
|
27
|
+
attr_reader :namespace
|
28
|
+
|
29
|
+
def initialize(msg=nil, container_uuid=nil, container_name=nil, namespace=nil)
|
30
|
+
@container_uuid = container_uuid
|
31
|
+
@container_name = container_name
|
32
|
+
@namespace = namespace
|
33
|
+
super(msg)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
m = super
|
38
|
+
m+= ": #{@container_name}" if not @container_name.nil?
|
39
|
+
m+= ": #{@namespace}" if not @namespace.nil?
|
40
|
+
m
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class FrontendHttpServerNameException < FrontendHttpServerException
|
46
|
+
attr_reader :server_name
|
47
|
+
|
48
|
+
def initialize(msg=nil, container_uuid=nil, container_name=nil, namespace=nil, server_name=nil)
|
49
|
+
@server_name = server_name
|
50
|
+
super(msg, container_uuid, container_name, namespace)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
m = super
|
55
|
+
m+= ": #{@server_name}" if not @server_name.nil?
|
56
|
+
m
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class FrontendHttpServerAliasException < FrontendHttpServerException
|
62
|
+
attr_reader :alias_name
|
63
|
+
|
64
|
+
def initialize(msg=nil, container_uuid=nil, container_name=nil, namespace=nil, alias_name=nil)
|
65
|
+
@alias_name = alias_name
|
66
|
+
super(msg, container_uuid, container_name, namespace)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
m = super
|
71
|
+
m+= ": #{@alias_name}" if not @alias_name.nil?
|
72
|
+
m
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# == Frontend Http Server
|
78
|
+
#
|
79
|
+
# Represents the front-end HTTP server on the system.
|
80
|
+
#
|
81
|
+
# Note: This is the Apache VirtualHost implementation; other implementations may vary.
|
82
|
+
#
|
83
|
+
class FrontendHttpServer < Model
|
84
|
+
include OpenShift::Utils::ShellExec
|
85
|
+
|
86
|
+
attr_reader :container_uuid, :container_name
|
87
|
+
attr_reader :namespace
|
88
|
+
|
89
|
+
def initialize(container_uuid, container_name, namespace)
|
90
|
+
Syslog.open('openshift-origin-node', Syslog::LOG_PID, Syslog::LOG_LOCAL0) unless Syslog.opened?
|
91
|
+
|
92
|
+
@config = OpenShift::Config.new
|
93
|
+
@container_uuid = container_uuid
|
94
|
+
@container_name = container_name
|
95
|
+
@namespace = namespace
|
96
|
+
@cloud_domain = @config.get("CLOUD_DOMAIN")
|
97
|
+
end
|
98
|
+
|
99
|
+
# Public: Initialize an empty configuration for this gear
|
100
|
+
#
|
101
|
+
# Examples
|
102
|
+
#
|
103
|
+
# create
|
104
|
+
# # => nil
|
105
|
+
# # directory for httpd configuration.
|
106
|
+
#
|
107
|
+
# Returns nil on Success or raises on Failure
|
108
|
+
def create
|
109
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
110
|
+
|
111
|
+
token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
|
112
|
+
path = File.join(basedir, ".httpd.d", token)
|
113
|
+
|
114
|
+
FileUtils.rm_rf(path) if File.exist?(path)
|
115
|
+
FileUtils.mkdir_p(path)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Public: Remove the frontend httpd configuration for a gear.
|
119
|
+
#
|
120
|
+
# Examples
|
121
|
+
#
|
122
|
+
# destroy
|
123
|
+
# # => nil
|
124
|
+
#
|
125
|
+
# Returns nil on Success or raises on Failure
|
126
|
+
def destroy(async=true)
|
127
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
128
|
+
|
129
|
+
path = File.join(basedir, ".httpd.d", "#{container_uuid}_*")
|
130
|
+
FileUtils.rm_rf(Dir.glob(path))
|
131
|
+
|
132
|
+
reload_all(async)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Public: Connect a path element to a back-end URI for this namespace.
|
136
|
+
#
|
137
|
+
# Examples
|
138
|
+
#
|
139
|
+
# connect('/', 'http://127.0.250.1:8080/')
|
140
|
+
# connect('/phpmyadmin, ''http://127.0.250.2:8080/')
|
141
|
+
# connect('/socket, 'http://127.0.250.3:8080/', {:websocket=>1}
|
142
|
+
#
|
143
|
+
# # => nil
|
144
|
+
#
|
145
|
+
# Returns nil on Success or raises on Failure
|
146
|
+
def connect(path, uri, options={})
|
147
|
+
raise NotImplementedError
|
148
|
+
end
|
149
|
+
|
150
|
+
# Public: Disconnect a path element from this namespace
|
151
|
+
#
|
152
|
+
# Examples
|
153
|
+
#
|
154
|
+
# disconnect('/')
|
155
|
+
# disconnect('/phpmyadmin)
|
156
|
+
#
|
157
|
+
# # => nil
|
158
|
+
#
|
159
|
+
# Returns nil on Success or raises on Failure
|
160
|
+
def disconnect(path)
|
161
|
+
raise NotImplementedError
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public: Add an alias to this namespace
|
165
|
+
#
|
166
|
+
# Examples
|
167
|
+
#
|
168
|
+
# add_alias("foo.example.com")
|
169
|
+
# # => nil
|
170
|
+
#
|
171
|
+
# Returns nil on Success or raises on Failure
|
172
|
+
def add_alias(name)
|
173
|
+
dname = clean_server_name(name)
|
174
|
+
path = server_alias_path(dname)
|
175
|
+
|
176
|
+
# Aliases must be globally unique across all gears.
|
177
|
+
existing = server_alias_search(dname, true)
|
178
|
+
if not existing.empty?
|
179
|
+
raise FrontendHttpServerAliasException.new("Already exists", @container_uuid, \
|
180
|
+
@container_name, @namespace, dname )
|
181
|
+
end
|
182
|
+
|
183
|
+
File.open(path, "w") do |f|
|
184
|
+
f.write("ServerAlias #{dname}")
|
185
|
+
f.flush
|
186
|
+
end
|
187
|
+
|
188
|
+
create_routes_alias(dname)
|
189
|
+
|
190
|
+
reload_all
|
191
|
+
end
|
192
|
+
|
193
|
+
# Public: Removes an alias from this namespace
|
194
|
+
#
|
195
|
+
# Examples
|
196
|
+
#
|
197
|
+
# add_alias("foo.example.com")
|
198
|
+
# # => nil
|
199
|
+
#
|
200
|
+
# Returns nil on Success or raises on Failure
|
201
|
+
def remove_alias(name)
|
202
|
+
dname = clean_server_name(name)
|
203
|
+
routes_file_path = server_routes_alias_path(dname)
|
204
|
+
|
205
|
+
FileUtils.rm_f(routes_file_path) if File.exist?(routes_file_path)
|
206
|
+
|
207
|
+
server_alias_search(dname, false).each do |path|
|
208
|
+
FileUtils.rm_f(path)
|
209
|
+
end
|
210
|
+
|
211
|
+
reload_all
|
212
|
+
end
|
213
|
+
|
214
|
+
# Private: Validate the server name
|
215
|
+
#
|
216
|
+
# The name is validated against DNS host name requirements from
|
217
|
+
# RFC 1123 and RFC 952. Additionally, OpenShift does not allow
|
218
|
+
# names/aliases to be an IP address.
|
219
|
+
def clean_server_name(name)
|
220
|
+
dname = name.downcase
|
221
|
+
|
222
|
+
if not dname.index(/[^0-9a-z\-.]/).nil?
|
223
|
+
raise FrontendHttpServerNameException.new("Invalid characters", @container_uuid, \
|
224
|
+
@container_name, @namespace, dname )
|
225
|
+
end
|
226
|
+
|
227
|
+
if dname.length > 255
|
228
|
+
raise FrontendHttpServerNameException.new("Too long", @container_uuid, \
|
229
|
+
@container_name, @namespace, dname )
|
230
|
+
end
|
231
|
+
|
232
|
+
if dname.length == 0
|
233
|
+
raise FrontendHttpServerNameException.new("Name was blank", @container_uuid, \
|
234
|
+
@container_name, @namespace, dname )
|
235
|
+
end
|
236
|
+
|
237
|
+
if dname =~ /^\d+\.\d+\.\d+\.\d+$/
|
238
|
+
raise FrontendHttpServerNameException.new("IP addresses are not allowed", @container_uuid, \
|
239
|
+
@container_name, @namespace, dname )
|
240
|
+
end
|
241
|
+
|
242
|
+
return dname
|
243
|
+
end
|
244
|
+
|
245
|
+
# Private: Return path to alias file
|
246
|
+
def server_alias_path(name)
|
247
|
+
dname = clean_server_name(name)
|
248
|
+
|
249
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
250
|
+
token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
|
251
|
+
path = File.join(basedir, '.httpd.d', token, "server_alias-#{dname}.conf")
|
252
|
+
end
|
253
|
+
|
254
|
+
# Private: Return path to routes alias file name used by ws proxy server
|
255
|
+
def server_routes_alias_path(name)
|
256
|
+
dname = clean_server_name(name)
|
257
|
+
|
258
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
259
|
+
token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
|
260
|
+
path = File.join(basedir, '.httpd.d', token, "routes_alias-#{dname}.json")
|
261
|
+
end
|
262
|
+
|
263
|
+
# Get path to the default routes.json file created for the node web proxy
|
264
|
+
def default_routes_path
|
265
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
266
|
+
token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
|
267
|
+
File.join(basedir, '.httpd.d', token, "routes.json")
|
268
|
+
end
|
269
|
+
|
270
|
+
# Create an alias routing file for the node web proxy server
|
271
|
+
def create_routes_alias(alias_name)
|
272
|
+
route_file = default_routes_path
|
273
|
+
alias_file = File.join(File.dirname(route_file), "routes_alias-#{alias_name}.json")
|
274
|
+
|
275
|
+
begin
|
276
|
+
File.open(route_file, 'r') do |fin|
|
277
|
+
File.open(alias_file, 'w') do |fout|
|
278
|
+
fout.write(fin.read.gsub("#{@container_name}-#{@namespace}.#{@cloud_domain}", "#{alias_name}"))
|
279
|
+
end
|
280
|
+
end
|
281
|
+
rescue => e
|
282
|
+
Syslog.alert("ERROR: Failure trying to create routes alias json file: #{@uuid} Exception: #{e.inspect}")
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Reload both Apache and the proxy server and combine output/return
|
287
|
+
def reload_all(async=false)
|
288
|
+
output = ''
|
289
|
+
errout = ''
|
290
|
+
retc = 0
|
291
|
+
|
292
|
+
out, err, rc = reload_node_web_proxy
|
293
|
+
output << out
|
294
|
+
errout << err
|
295
|
+
retc = rc if rc != 0
|
296
|
+
|
297
|
+
out, err, rc = reload_httpd(async)
|
298
|
+
output << out
|
299
|
+
errout << err
|
300
|
+
retc = rc if rc != 0
|
301
|
+
|
302
|
+
return output, errout, retc
|
303
|
+
end
|
304
|
+
|
305
|
+
# Reload the Apache configuration
|
306
|
+
def reload_httpd(async=false)
|
307
|
+
async_opt="-b" if async
|
308
|
+
out, err, rc = shellCmd("/usr/sbin/oo-httpd-singular #{async_opt} graceful")
|
309
|
+
Syslog.alert("ERROR: failure from oo-httpd-singular(#{rc}): #{@uuid} stdout: #{out} stderr:#{err}") unless rc == 0
|
310
|
+
return out, err, rc
|
311
|
+
end
|
312
|
+
|
313
|
+
# Reload the configuration of the node web proxy server
|
314
|
+
def reload_node_web_proxy
|
315
|
+
out, err, rc = shellCmd("service openshift-node-web-proxy reload")
|
316
|
+
Syslog.alert("ERROR: failure from openshift-node-web-proxy(#{rc}): #{@uuid} stdout: #{out} stderr:#{err}") unless rc == 0
|
317
|
+
return out, err, rc
|
318
|
+
end
|
319
|
+
|
320
|
+
# Private: Search for matching alias files
|
321
|
+
# Previously, case sensitive matches were used
|
322
|
+
# checking for a duplicate and there are mixed
|
323
|
+
# case aliases in use.
|
324
|
+
def server_alias_search(name, all_gears=false)
|
325
|
+
dname = clean_server_name(name)
|
326
|
+
resp = []
|
327
|
+
|
328
|
+
basedir = @config.get("GEAR_BASE_DIR")
|
329
|
+
if all_gears
|
330
|
+
token = "*"
|
331
|
+
else
|
332
|
+
token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
|
333
|
+
end
|
334
|
+
srch = File.join(basedir, ".httpd.d", token, "server_alias-*.conf")
|
335
|
+
Dir.glob(srch).each do |fn|
|
336
|
+
cmpname = fn.sub(/^.*\/server_alias-(.*)\.conf$/, '\\1')
|
337
|
+
if cmpname.casecmp(dname) == 0
|
338
|
+
resp << fn
|
339
|
+
end
|
340
|
+
end
|
341
|
+
resp
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|