openshift-origin-node 1.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of openshift-origin-node might be problematic. Click here for more details.

Files changed (51) hide show
  1. data/COPYRIGHT +1 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +11 -0
  4. data/README.md +3 -0
  5. data/Rakefile +28 -0
  6. data/bin/oo-add-alias +93 -0
  7. data/bin/oo-app-create +110 -0
  8. data/bin/oo-app-destroy +100 -0
  9. data/bin/oo-app-state-show +74 -0
  10. data/bin/oo-authorized-ssh-key-add +83 -0
  11. data/bin/oo-authorized-ssh-key-remove +82 -0
  12. data/bin/oo-broker-auth-key-add +84 -0
  13. data/bin/oo-broker-auth-key-remove +72 -0
  14. data/bin/oo-cartridge-info +70 -0
  15. data/bin/oo-cartridge-list +70 -0
  16. data/bin/oo-connector-execute +94 -0
  17. data/bin/oo-env-var-add +81 -0
  18. data/bin/oo-env-var-remove +78 -0
  19. data/bin/oo-get-quota +64 -0
  20. data/bin/oo-remove-alias +93 -0
  21. data/bin/oo-set-quota +59 -0
  22. data/conf/node.conf +30 -0
  23. data/conf/resource_limits.template +67 -0
  24. data/lib/openshift-origin-node.rb +29 -0
  25. data/lib/openshift-origin-node/config.rb +21 -0
  26. data/lib/openshift-origin-node/environment.rb +26 -0
  27. data/lib/openshift-origin-node/model/application_container.rb +298 -0
  28. data/lib/openshift-origin-node/model/frontend_httpd.rb +346 -0
  29. data/lib/openshift-origin-node/model/node.rb +134 -0
  30. data/lib/openshift-origin-node/model/unix_user.rb +738 -0
  31. data/lib/openshift-origin-node/plugins/unix_user_observer.rb +86 -0
  32. data/lib/openshift-origin-node/utils/shell_exec.rb +115 -0
  33. data/lib/openshift-origin-node/version.rb +23 -0
  34. data/misc/bin/oo-admin-ctl-cgroups +482 -0
  35. data/misc/bin/oo-cgroup-read +25 -0
  36. data/misc/bin/oo-get-mcs-level +29 -0
  37. data/misc/bin/oo-trap-user +248 -0
  38. data/misc/bin/rhcsh +155 -0
  39. data/misc/bin/setup_pam_fs_limits.sh +146 -0
  40. data/misc/bin/teardown_pam_fs_limits.sh +73 -0
  41. data/misc/doc/cgconfig.conf +26 -0
  42. data/misc/etc/openshift-run.conf +1 -0
  43. data/misc/init/openshift-cgroups +56 -0
  44. data/misc/services/openshift-cgroups.service +14 -0
  45. data/openshift-origin-node.gemspec +31 -0
  46. data/rubygem-openshift-origin-node.spec +263 -0
  47. data/test/test_helper.rb +20 -0
  48. data/test/unit/frontend_httpd_test.rb +144 -0
  49. data/test/unit/unix_user_test.rb +95 -0
  50. data/test/unit/version_test.rb +45 -0
  51. 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