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,134 @@
1
+ #--
2
+ # Copyright 2012 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 'openshift-origin-node'
18
+ require 'openshift-origin-common'
19
+ require 'systemu'
20
+
21
+ module OpenShift
22
+ class NodeCommandException < StandardError; end
23
+
24
+ class Node < Model
25
+ def self.get_cartridge_list(list_descriptors = false, porcelain = false, oo_debug = false)
26
+ carts = []
27
+
28
+ cartridge_path = OpenShift::Config.new.get("CARTRIDGE_BASE_PATH")
29
+ Dir.foreach(cartridge_path) do |cart_dir|
30
+ next if [".", "..", "embedded", "abstract", "abstract-httpd", "abstract-jboss"].include? cart_dir
31
+ path = File.join(cartridge_path, cart_dir, "info", "manifest.yml")
32
+ begin
33
+ print "Loading #{cart_dir}..." if oo_debug
34
+ carts.push OpenShift::Cartridge.new.from_descriptor(YAML.load(File.open(path)))
35
+ print "OK\n" if oo_debug
36
+ rescue Exception => e
37
+ print "ERROR\n" if oo_debug
38
+ print "#{e.message}\n#{e.backtrace.inspect}\n" unless porcelain
39
+ end
40
+ end
41
+
42
+ print "\n\n\n" if oo_debug
43
+
44
+ output = ""
45
+ if porcelain
46
+ if list_descriptors
47
+ output << "CLIENT_RESULT: "
48
+ output << carts.map{|c| c.to_descriptor.to_yaml}.to_json
49
+ else
50
+ output << "CLIENT_RESULT: "
51
+ output << carts.map{|c| c.name}.to_json
52
+ end
53
+ else
54
+ if list_descriptors
55
+ carts.each do |c|
56
+ output << "Cartridge name: #{c.name}\n\nDescriptor:\n #{c.to_descriptor.inspect}\n\n\n"
57
+ end
58
+ else
59
+ output << "Cartridges:\n"
60
+ carts.each do |c|
61
+ output << "\t#{c.name}\n"
62
+ end
63
+ end
64
+ end
65
+ output
66
+ end
67
+
68
+ def self.get_cartridge_info(cart_name, porcelain = false, oo_debug = false)
69
+ output = ""
70
+ cart_found = false
71
+
72
+ cartridge_path = OpenShift::Config.new.get("CARTRIDGE_BASE_PATH")
73
+ Dir.foreach(cartridge_path) do |cart_dir|
74
+ next if [".", "..", "embedded", "abstract", "abstract-httpd", "haproxy-1.4", "mysql-5.1", "mongodb-2.2", "postgresql-8.4"].include? cart_dir
75
+ path = File.join(cartridge_path, cart_dir, "info", "manifest.yml")
76
+ begin
77
+ cart = OpenShift::Cartridge.new.from_descriptor(YAML.load(File.open(path)))
78
+ if cart.name == cart_name
79
+ output << "CLIENT_RESULT: "
80
+ output << cart.to_descriptor.to_json
81
+ cart_found = true
82
+ break
83
+ end
84
+ rescue Exception => e
85
+ print "ERROR\n" if oo_debug
86
+ print "#{e.message}\n#{e.backtrace.inspect}\n" unless porcelain
87
+ end
88
+ end
89
+
90
+ embedded_cartridge_path = File.join(cartridge_path, "embedded")
91
+ if (! cart_found) and File.directory?(embedded_cartridge_path)
92
+ Dir.foreach(embedded_cartridge_path) do |cart_dir|
93
+ next if [".",".."].include? cart_dir
94
+ path = File.join(embedded_cartridge_path, cart_dir, "info", "manifest.yml")
95
+ begin
96
+ cart = OpenShift::Cartridge.new.from_descriptor(YAML.load(File.open(path)))
97
+ if cart.name == cart_name
98
+ output << "CLIENT_RESULT: "
99
+ output << cart.to_descriptor.to_json
100
+ break
101
+ end
102
+ rescue Exception => e
103
+ print "ERROR\n" if oo_debug
104
+ print "#{e.message}\n#{e.backtrace.inspect}\n" unless porcelain
105
+ end
106
+ end
107
+ end
108
+ output
109
+ end
110
+
111
+ def self.get_quota(uuid)
112
+ cmd = %&quota -w #{uuid} | awk '/^.*\\/dev/ {print $1":"$2":"$3":"$4":"$5":"$6":"$7}'; exit ${PIPESTATUS[0]}&
113
+ st, out, errout = systemu cmd
114
+ if st.exitstatus == 0 || st.exitstatus == 1
115
+ arr = out.strip.split(":")
116
+ raise NodeCommandException.new "Error: #{errout} executing command #{cmd}" unless arr.length == 7
117
+ arr
118
+ else
119
+ raise NodeCommandException.new "Error: #{errout} executing command #{cmd}"
120
+ end
121
+ end
122
+
123
+ def self.set_quota(uuid, blocksmax, inodemax)
124
+ cur_quota = get_quota(uuid)
125
+ inodemax = cur_quota[6] if inodemax.to_s.empty?
126
+
127
+ mountpoint = %x[quota -w #{uuid} | awk '/^.*\\/dev/ {print $1}']
128
+ cmd = "setquota -u #{uuid} 0 #{blocksmax} 0 #{inodemax} -a #{mountpoint}"
129
+ st, out, errout = systemu cmd
130
+ raise NodeCommandException.new "Error: #{errout} executing command #{cmd}" unless st.exitstatus == 0
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,738 @@
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-node/model/frontend_httpd.rb'
20
+ require 'openshift-origin-common'
21
+ require 'syslog'
22
+ require 'fcntl'
23
+
24
+ module OpenShift
25
+ class UserCreationException < Exception
26
+ end
27
+
28
+ class UserDeletionException < Exception
29
+ end
30
+
31
+ # == Unix User
32
+ #
33
+ # Represents a user account on the system.
34
+ class UnixUser < Model
35
+ include OpenShift::Utils::ShellExec
36
+ attr_reader :uuid, :uid, :gid, :gecos, :homedir, :application_uuid,
37
+ :container_uuid, :app_name, :namespace, :quota_blocks, :quota_files,
38
+ :container_name
39
+ attr_accessor :debug
40
+
41
+ DEFAULT_SKEL_DIR = File.join(OpenShift::Config::CONF_DIR,"skel")
42
+
43
+ def initialize(application_uuid, container_uuid, user_uid=nil,
44
+ app_name=nil, container_name=nil, namespace=nil, quota_blocks=nil, quota_files=nil, debug=false)
45
+ Syslog.open('openshift-origin-node', Syslog::LOG_PID, Syslog::LOG_LOCAL0) unless Syslog.opened?
46
+
47
+ @config = OpenShift::Config.new
48
+
49
+ @container_uuid = container_uuid
50
+ @application_uuid = application_uuid
51
+ @uuid = container_uuid
52
+ @app_name = app_name
53
+ @container_name = container_name
54
+ @namespace = namespace
55
+ @quota_blocks = quota_blocks
56
+ @quota_files = quota_files
57
+ @debug = debug
58
+
59
+ begin
60
+ user_info = Etc.getpwnam(@uuid)
61
+ @uid = user_info.uid
62
+ @gid = user_info.gid
63
+ @gecos = user_info.gecos
64
+ @homedir = "#{user_info.dir}/"
65
+ rescue ArgumentError => e
66
+ @uid = user_uid
67
+ @gid = user_uid
68
+ @gecos = nil
69
+ @homedir = nil
70
+ end
71
+ end
72
+
73
+ def name
74
+ @uuid
75
+ end
76
+
77
+ # Public: Create an empty gear.
78
+ #
79
+ # Examples
80
+ #
81
+ # create
82
+ # # => nil
83
+ # # a user
84
+ # # Setup permissions
85
+ #
86
+ # Returns nil on Success or raises on Failure
87
+ def create
88
+ skel_dir = @config.get("GEAR_SKEL_DIR") || DEFAULT_SKEL_DIR
89
+ shell = @config.get("GEAR_SHELL") || "/bin/bash"
90
+ gecos = @config.get("GEAR_GECOS") || "OO application container"
91
+ notify_observers(:before_unix_user_create)
92
+ basedir = @config.get("GEAR_BASE_DIR")
93
+
94
+ # lock to prevent race condition between create and delete of gear
95
+ uuid_lock_file = "/var/lock/oo-create.#{@uuid}"
96
+ File.open(uuid_lock_file, File::RDWR|File::CREAT, 0o0600) do | uuid_lock |
97
+ uuid_lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
98
+ uuid_lock.flock(File::LOCK_EX)
99
+
100
+ # Lock to prevent race condition on obtaining a UNIX user uid.
101
+ # When running without districts, there is a simple search on the
102
+ # passwd file for the next available uid.
103
+ File.open("/var/lock/oo-create", File::RDWR|File::CREAT, 0o0600) do | uid_lock |
104
+ uid_lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
105
+ uid_lock.flock(File::LOCK_EX)
106
+
107
+ unless @uid
108
+ @uid = @gid = next_uid
109
+ end
110
+
111
+ unless @homedir
112
+ @homedir = File.join(basedir,@uuid)
113
+ end
114
+
115
+ cmd = %{useradd -u #{@uid} \
116
+ -d #{@homedir} \
117
+ -s #{shell} \
118
+ -c '#{gecos}' \
119
+ -m \
120
+ -k #{skel_dir} \
121
+ #{@uuid}}
122
+ out,err,rc = shellCmd(cmd)
123
+ raise UserCreationException.new(
124
+ "ERROR: unable to create user account(#{rc}): #{cmd.squeeze(" ")} stdout: #{out} stderr: #{err}"
125
+ ) unless rc == 0
126
+
127
+ FileUtils.chown("root", @uuid, @homedir)
128
+ FileUtils.chmod 0o0750, @homedir
129
+
130
+ if @config.get("CREATE_APP_SYMLINKS").to_i == 1
131
+ unobfuscated = File.join(File.dirname(@homedir),"#{@container_name}-#{namespace}")
132
+ if not File.exists? unobfuscated
133
+ FileUtils.ln_s File.basename(@homedir), unobfuscated, :force=>true
134
+ end
135
+ end
136
+ end
137
+ notify_observers(:after_unix_user_create)
138
+ initialize_homedir(basedir, @homedir, @config.get("CARTRIDGE_BASE_PATH"))
139
+ initialize_openshift_port_proxy
140
+
141
+ uuid_lock.flock(File::LOCK_UN)
142
+ File.unlink(uuid_lock_file)
143
+ end
144
+ end
145
+
146
+ # Public: Destroys a gear stopping all processes and removing all files
147
+ #
148
+ # The order of the calls and gyrations done in this code is to prevent
149
+ # pam_namespace from locking polyinstantiated directories during
150
+ # their deletion. If you see "broken" gears, i.e. ~uuid/.tmp and
151
+ # ~/uuid/.sandbox after #destroy has been called, this method is broken.
152
+ # See Bug 853582 for history.
153
+ #
154
+ # Examples
155
+ #
156
+ # destroy
157
+ # # => nil
158
+ #
159
+ # Returns nil on Success or raises on Failure
160
+ def destroy
161
+ if @uid.nil? and (not File.directory?(@homedir.to_s))
162
+ # gear seems to have been destroyed already... suppress any error
163
+ # TODO : remove remaining stuff if it exists, e.g. .httpd/#{uuid}* etc
164
+ return nil
165
+ end
166
+ raise UserDeletionException.new(
167
+ "ERROR: unable to destroy user account #{@uuid}"
168
+ ) if @uid.nil? || @homedir.nil? || @uuid.nil?
169
+
170
+ # Don't try to delete a gear that is being scaled-up|created|deleted
171
+ uuid_lock_file = "/var/lock/oo-create.#{@uuid}"
172
+ File.open(uuid_lock_file, File::RDWR|File::CREAT, 0o0600) do | lock |
173
+ lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
174
+ lock.flock(File::LOCK_EX)
175
+
176
+ # These calls and their order is designed to release pam_namespace's
177
+ # locks on .tmp and .sandbox. Change then at your peril.
178
+ #
179
+ # 1. Kill off the easy processes
180
+ # 2. Lock down the user from creating new processes (cgroups freeze, nprocs 0)
181
+ # 3. Attempt to move any processes that didn't die into state 'D' (re: cgroups freeze)
182
+ self.class.kill_procs(@uid)
183
+ notify_observers(:before_unix_user_destroy)
184
+ self.class.kill_procs(@uid)
185
+
186
+ purge_sysvipc(uuid)
187
+ initialize_openshift_port_proxy
188
+
189
+ if @config.get("CREATE_APP_SYMLINKS").to_i == 1
190
+ Dir.foreach(File.dirname(@homedir)) do |dent|
191
+ unobfuscate = File.join(File.dirname(@homedir), dent)
192
+ if (File.symlink?(unobfuscate)) &&
193
+ (File.readlink(unobfuscate) == File.basename(@homedir))
194
+ File.unlink(unobfuscate)
195
+ end
196
+ end
197
+ end
198
+
199
+ OpenShift::FrontendHttpServer.new(@container_uuid,@container_name,@namespace).destroy
200
+
201
+ dirs = list_home_dir(@homedir)
202
+ cmd = "userdel -f \"#{@uuid}\""
203
+ out,err,rc = shellCmd(cmd)
204
+ raise UserDeletionException.new(
205
+ "ERROR: unable to destroy user account(#{rc}): #{cmd} stdout: #{out} stderr: #{err}"
206
+ ) unless rc == 0
207
+
208
+ # 1. Don't believe everything you read on the userdel man page...
209
+ # 2. If there are any active processes left pam_namespace is not going
210
+ # to let polyinstantiated directories be deleted.
211
+ FileUtils.rm_rf(@homedir)
212
+ if File.exists?(@homedir)
213
+ # Ops likes the verbose verbage
214
+ Syslog.alert %Q{
215
+ 1st attempt to remove \'#{@homedir}\' from filesystem failed.
216
+ Dir(before) #{@uuid}/#{@uid} => #{dirs}
217
+ Dir(after) #{@uuid}/#{@uid} => #{list_home_dir(@homedir)}
218
+ }
219
+ end
220
+
221
+ # release resources (cgroups thaw), this causes Zombies to get killed
222
+ notify_observers(:after_unix_user_destroy)
223
+
224
+ # try one last time...
225
+ if File.exists?(@homedir)
226
+ sleep(5) # don't fear the reaper
227
+ FileUtils.rm_rf(@homedir) # This is our last chance to nuke the polyinstantiated directories
228
+ Syslog.alert "2nd attempt to remove \'#{@homedir}\' from filesystem failed." if File.exists?(@homedir)
229
+ end
230
+
231
+ lock.flock(File::LOCK_UN)
232
+ File.unlink(uuid_lock_file)
233
+ end
234
+ end
235
+
236
+ # Public: Append an SSH key to a users authorized_keys file
237
+ #
238
+ # key - The String value of the ssh key.
239
+ # key_type - The String value of the key type ssh-(rsa|dss)).
240
+ # comment - The String value of the comment to append to the key.
241
+ #
242
+ # Examples
243
+ #
244
+ # add_ssh_key('AAAAB3NzaC1yc2EAAAADAQABAAABAQDE0DfenPIHn5Bq/...',
245
+ # 'ssh-rsa',
246
+ # 'example@example.com')
247
+ # # => nil
248
+ #
249
+ # Returns nil on Success or raises on Failure
250
+ def add_ssh_key(key, key_type=nil, comment=nil)
251
+ comment = "" unless comment
252
+ self.class.notify_observers(:before_add_ssh_key, self, key)
253
+
254
+ authorized_keys_file = File.join(@homedir, ".ssh", "authorized_keys")
255
+ keys = read_ssh_keys authorized_keys_file
256
+ key_type = "ssh-rsa" if key_type.to_s.strip.length == 0
257
+ cloud_name = @config.get("CLOUD_NAME") || "OPENSHIFT"
258
+ ssh_comment = "#{cloud_name}-#{@uuid}#{comment}"
259
+ shell = @config.get("GEAR_SHELL") || "/bin/bash"
260
+ cmd_entry = "command=\"#{shell}\",no-X11-forwarding #{key_type} #{key} #{ssh_comment}"
261
+
262
+ keys[ssh_comment] = cmd_entry
263
+ write_ssh_keys authorized_keys_file, keys
264
+
265
+ self.class.notify_observers(:after_add_ssh_key, self, key)
266
+ end
267
+
268
+ # Public: Remove an SSH key from a users authorized_keys file.
269
+ #
270
+ # key - The String value of the ssh key.
271
+ # comment - The String value of the comment associated with the key.
272
+ #
273
+ # Examples
274
+ #
275
+ # remove_ssh_key('AAAAB3NzaC1yc2EAAAADAQABAAABAQDE0DfenPIHn5Bq/...',
276
+ # 'example@example.com')
277
+ # # => nil
278
+ #
279
+ # Returns nil on Success or raises on Failure
280
+ def remove_ssh_key(key, comment=nil)
281
+ self.class.notify_observers(:before_remove_ssh_key, self, key)
282
+
283
+ authorized_keys_file = File.join(@homedir, ".ssh", "authorized_keys")
284
+ keys = read_ssh_keys authorized_keys_file
285
+
286
+ if comment
287
+ keys.delete_if{ |k, v| v.include?(key) && v.include?(comment)}
288
+ else
289
+ keys.delete_if{ |k, v| v.include?(key)}
290
+ end
291
+
292
+ write_ssh_keys authorized_keys_file, keys
293
+
294
+ self.class.notify_observers(:after_remove_ssh_key, self, key)
295
+ end
296
+
297
+ # Public: Add an environment variable to a given gear.
298
+ #
299
+ # key - The String value of target environment variable.
300
+ # value - The String value to place inside the environment variable.
301
+ # prefix_cloud_name - The String value to append in front of key.
302
+ #
303
+ # Examples
304
+ #
305
+ # add_env_var('mysql-5.3')
306
+ # # => 36
307
+ #
308
+ # Returns the Integer value for how many bytes got written or raises on
309
+ # failure.
310
+ def add_env_var(key, value, prefix_cloud_name = false, &blk)
311
+ env_dir = File.join(@homedir,'.env/')
312
+ if prefix_cloud_name
313
+ key = (@config.get('CLOUD_NAME') || 'OPENSHIFT') + "_#{key}"
314
+ end
315
+ filename = File.join(env_dir, key)
316
+ File.open(filename,
317
+ File::WRONLY|File::TRUNC|File::CREAT) do |file|
318
+ file.write "export #{key}='#{value}'"
319
+ end
320
+
321
+ if block_given?
322
+ blk.call(value)
323
+ end
324
+ end
325
+
326
+ # Public: list directories (cartridges) in home directory
327
+ # @param [String] home directory
328
+ # @return [String] comma separated list of directories
329
+ def list_home_dir(home_dir)
330
+ results = []
331
+ Dir.foreach(home_dir) do |entry|
332
+ #next if entry =~ /^\.{1,2}/ # Ignore ".", "..", or hidden files
333
+ results << entry
334
+ end
335
+ results.join(', ')
336
+ end
337
+
338
+ # Public: Remove an environment variable from a given gear.
339
+ #
340
+ # key - String name of the environment variable to remove.
341
+ # prefix_cloud_name - String prefix to append to key.
342
+ #
343
+ # Examples
344
+ #
345
+ # remove_env_var('OPENSHIFT_MONGODB_DB_URL')
346
+ # # => nil
347
+ #
348
+ # Returns an nil on success and false on failure.
349
+ def remove_env_var(key, prefix_cloud_name=false)
350
+ status = false
351
+ [".env", ".env/.uservars"].each do |path|
352
+ env_dir = File.join(@homedir,path)
353
+ if prefix_cloud_name
354
+ key = (@config.get("CLOUD_NAME") || "OPENSHIFT") + "_#{key}"
355
+ end
356
+ env_file_path = File.join(env_dir, key)
357
+ FileUtils.rm_f env_file_path
358
+ status = status ? true : (File.exists?(env_file_path) ? false : true)
359
+ end
360
+ status
361
+ end
362
+
363
+ # Public: Add broker authorization keys so gear can communicate with
364
+ # broker.
365
+ #
366
+ # iv - A String value for the IV file.
367
+ # token - A String value for the token file.
368
+ #
369
+ # Examples
370
+ # add_broker_auth('ivvalue', 'tokenvalue')
371
+ # # => ["/var/lib/openshift/UUID/.auth/iv",
372
+ # "/var/lib/openshift/UUID/.auth/token"]
373
+ #
374
+ # Returns An Array of Strings for the newly created auth files
375
+ def add_broker_auth(iv,token)
376
+ broker_auth_dir=File.join(@homedir,'.auth')
377
+ FileUtils.mkdir_p broker_auth_dir
378
+ File.open(File.join(broker_auth_dir, 'iv'),
379
+ File::WRONLY|File::TRUNC|File::CREAT) do |file|
380
+ file.write iv
381
+ end
382
+ File.open(File.join(broker_auth_dir, 'token'),
383
+ File::WRONLY|File::TRUNC|File::CREAT) do |file|
384
+ file.write token
385
+ end
386
+
387
+ FileUtils.chown_R("root", @uuid,broker_auth_dir)
388
+ FileUtils.chmod(0o0750, broker_auth_dir)
389
+ FileUtils.chmod(0o0640, Dir.glob("#{broker_auth_dir}/*"))
390
+ end
391
+
392
+ # Public: Remove broker authentication keys from gear.
393
+ #
394
+ # Examples
395
+ # remove_broker_auth
396
+ # # => nil
397
+ #
398
+ # Returns nil on Success and false on Failure
399
+ def remove_broker_auth
400
+ broker_auth_dir=File.join(@homedir, '.auth')
401
+ FileUtils.rm_rf broker_auth_dir
402
+ File.exists?(broker_auth_dir) ? false : true
403
+ end
404
+
405
+ #private
406
+
407
+ # Private: Create and populate the users home dir.
408
+ #
409
+ # Examples
410
+ # initialize_homedir
411
+ # # => nil
412
+ # # Creates:
413
+ # # ~
414
+ # # ~/.tmp/
415
+ # # ~/.sandbox/$uuid
416
+ # # ~/.env/
417
+ # # APP_UUID, GEAR_UUID, APP_NAME, APP_DNS, HOMEDIR, DATA_DIR, \
418
+ # # GEAR_DNS, GEAR_NAME, PATH, REPO_DIR, TMP_DIR, HISTFILE
419
+ # # ~/app-root
420
+ # # ~/app-root/data
421
+ # # ~/app-root/runtime/repo
422
+ # # ~/app-root/repo -> runtime/repo
423
+ # # ~/app-root/runtime/data -> ../data
424
+ #
425
+ # Returns nil on Success and raises on Failure.
426
+ def initialize_homedir(basedir, homedir, cart_basedir)
427
+ @homedir = homedir
428
+ notify_observers(:before_initialize_homedir)
429
+ homedir = homedir.end_with?('/') ? homedir : homedir + '/'
430
+
431
+ tmp_dir = File.join(homedir, ".tmp")
432
+ # Required for polyinstantiated tmp dirs to work
433
+ FileUtils.mkdir_p tmp_dir
434
+ FileUtils.chmod(0o0000, tmp_dir)
435
+
436
+ sandbox_dir = File.join(homedir, ".sandbox")
437
+ FileUtils.mkdir_p sandbox_dir
438
+ FileUtils.chmod(0o0000, sandbox_dir)
439
+
440
+ sandbox_uuid_dir = File.join(sandbox_dir, @uuid)
441
+ FileUtils.mkdir_p sandbox_uuid_dir
442
+ FileUtils.chmod(0o1755, sandbox_uuid_dir)
443
+
444
+ env_dir = File.join(homedir, ".env")
445
+ FileUtils.mkdir_p(env_dir)
446
+ FileUtils.chmod(0o0750, env_dir)
447
+ FileUtils.chown(nil, @uuid, env_dir)
448
+
449
+ ssh_dir = File.join(homedir, ".ssh")
450
+ FileUtils.mkdir_p(ssh_dir)
451
+ FileUtils.chmod(0o0750, ssh_dir)
452
+ FileUtils.chown(nil, @uuid, ssh_dir)
453
+
454
+ geardir = File.join(homedir, @container_name, "/")
455
+ gearappdir = File.join(homedir, "app-root", "/")
456
+
457
+ add_env_var("APP_DNS",
458
+ "#{@app_name}-#{@namespace}.#{@config.get("CLOUD_DOMAIN")}",
459
+ true)
460
+ add_env_var("APP_NAME", @app_name, true)
461
+ add_env_var("APP_UUID", @application_uuid, true)
462
+
463
+ data_dir = File.join(gearappdir, "data", "/")
464
+ add_env_var("DATA_DIR", data_dir, true) {|v|
465
+ FileUtils.mkdir_p(v, :verbose => @debug)
466
+ }
467
+ add_env_var("HISTFILE", File.join(data_dir, ".bash_history"))
468
+ profile = File.join(data_dir, ".bash_profile")
469
+ File.open(profile, File::WRONLY|File::TRUNC|File::CREAT, 0o0600) {|file|
470
+ file.write %Q{
471
+ # Warning: Be careful with modifications to this file,
472
+ # Your changes may cause your application to fail.
473
+ }
474
+ }
475
+ FileUtils.chown(@uuid, @uuid, profile, :verbose => @debug)
476
+
477
+
478
+ add_env_var("GEAR_DNS",
479
+ "#{@container_name}-#{@namespace}.#{@config.get("CLOUD_DOMAIN")}",
480
+ true)
481
+ add_env_var("GEAR_NAME", @container_name, true)
482
+ add_env_var("GEAR_UUID", @container_uuid, true)
483
+
484
+ add_env_var("HOMEDIR", homedir, true)
485
+
486
+ add_env_var("PATH",
487
+ "#{cart_basedir}abstract-httpd/info/bin/:#{cart_basedir}abstract/info/bin/:$PATH",
488
+ false)
489
+
490
+ add_env_var("REPO_DIR", File.join(gearappdir, "runtime", "repo", "/"), true) {|v|
491
+ FileUtils.mkdir_p(v, :verbose => @debug)
492
+ FileUtils.cd gearappdir do |d|
493
+ FileUtils.ln_s("runtime/repo", "repo", :verbose => @debug)
494
+ end
495
+ FileUtils.cd File.join(gearappdir, "runtime") do |d|
496
+ FileUtils.ln_s("../data", "data", :verbose => @debug)
497
+ end
498
+ }
499
+
500
+ add_env_var("TMP_DIR", "/tmp/", true)
501
+
502
+ # Update all directory entries ~/app-root/*
503
+ Dir[gearappdir + "/*"].entries.reject{|e| [".", ".."].include? e}.each {|e|
504
+ FileUtils.chmod_R(0o0750, e, :verbose => @debug)
505
+ FileUtils.chown_R(@uuid, @uuid, e, :verbose => @debug)
506
+ }
507
+ FileUtils.chown(nil, @uuid, gearappdir, :verbose => @debug)
508
+ raise "Failed to instantiate gear: missing application directory (#{gearappdir})" unless File.exist?(gearappdir)
509
+
510
+ state_file = File.join(gearappdir, "runtime", ".state")
511
+ File.open(state_file, File::WRONLY|File::TRUNC|File::CREAT, 0o0660) {|file|
512
+ file.write "new\n"
513
+ }
514
+ FileUtils.chown(@uuid, @uuid, state_file, :verbose => @debug)
515
+
516
+ OpenShift::FrontendHttpServer.new(@container_uuid,@container_name,@namespace).create
517
+
518
+ # Fix SELinux context
519
+ set_selinux_context(homedir)
520
+
521
+ notify_observers(:after_initialize_homedir)
522
+ end
523
+
524
+ # Private: Determine next available user id. This is usually determined
525
+ # and provided by the broker but is auto determined if not
526
+ # provided.
527
+ #
528
+ # Examples:
529
+ # next_uid =>
530
+ # # => 504
531
+ #
532
+ # Returns Integer value for next available uid.
533
+ def next_uid
534
+ uids = IO.readlines("/etc/passwd").map{ |line| line.split(":")[2].to_i }
535
+ gids = IO.readlines("/etc/group").map{ |line| line.split(":")[2].to_i }
536
+ min_uid = (@config.get("GEAR_MIN_UID") || "500").to_i
537
+ max_uid = (@config.get("GEAR_MAX_UID") || "1500").to_i
538
+
539
+ (min_uid..max_uid).each do |i|
540
+ if !uids.include?(i) and !gids.include?(i)
541
+ return i
542
+ end
543
+ end
544
+ end
545
+
546
+ # Private: Initialize OpenShift Port Proxy for this gear
547
+ #
548
+ # The port proxy range is determined by configuration and must
549
+ # produce identical results to the abstract cartridge provided
550
+ # range.
551
+ #
552
+ # Examples:
553
+ # initialize_openshift_port_proxy
554
+ # => true
555
+ # service openshift_port_proxy setproxy 35000 delete 35001 delete etc...
556
+ #
557
+ # Returns:
558
+ # true - port proxy could be initialized properly
559
+ # false - port proxy could not be initialized properly
560
+ def initialize_openshift_port_proxy
561
+ notify_observers(:before_initialize_openshift_port_proxy)
562
+
563
+ port_begin = (@config.get("PORT_BEGIN") || "35531").to_i
564
+ ports_per_user = (@config.get("PORTS_PER_USER") || "5").to_i
565
+
566
+ # Note, due to a mismatch between dev and prod this is
567
+ # intentionally not GEAR_MIN_UID and the range must
568
+ # wrap back around on itself.
569
+ uid_begin = (@config.get("UID_BEGIN") || "500").to_i
570
+
571
+ wrap_uid = ((65536 - port_begin)/ports_per_user)+uid_begin
572
+
573
+ if @uid >= wrap_uid
574
+ tuid = @uid - wrap_uid + uid_begin
575
+ else
576
+ tuid = @uid
577
+ end
578
+
579
+ proxy_port_begin = (tuid-uid_begin) * ports_per_user + port_begin
580
+
581
+ proxy_port_range = (proxy_port_begin ... (proxy_port_begin + ports_per_user))
582
+
583
+ cmd = %{openshift-port-proxy-cfg setproxy}
584
+ proxy_port_range.each { |i| cmd << " #{i} delete" }
585
+ out, err, rc = shellCmd(cmd)
586
+ Syslog.warning(
587
+ "WARNING: openshift-port-proxy-cfg failed(#{rc}): #{cmd} stdout: #{out} stderr: #{err}"
588
+ ) unless 0 == rc
589
+
590
+ notify_observers(:after_initialize_openshift_port_proxy)
591
+ return rc == 0
592
+ end
593
+
594
+
595
+ # Private: Kill all processes for a given gear
596
+ #
597
+ # Kill all processes owned by the uid or uuid.
598
+ # No reason for graceful shutdown first, the directories and user are going
599
+ # to be removed from the system.
600
+ #
601
+ # Examples:
602
+ # kill_gear_procs
603
+ # => true
604
+ # pkill -u id
605
+ #
606
+ # Raises exception on error.
607
+ #
608
+ def self.kill_procs(id)
609
+ if id.nil? or id == ""
610
+ raise ArgumentError, "Supplied ID must be a uid."
611
+ end
612
+
613
+ # Give it a good try to delete all processes.
614
+ # This abuse is neccessary to release locks on polyinstantiated
615
+ # directories by pam_namespace.
616
+ out = err = rc = nil
617
+ 10.times do |i|
618
+ OpenShift::Utils::ShellExec.shellCmd(%{/usr/bin/pkill -9 -u #{id}})
619
+ out,err,rc = OpenShift::Utils::ShellExec.shellCmd(%{/usr/bin/pgrep -u #{id}})
620
+ break unless 0 == rc
621
+
622
+ Syslog.alert "ERROR: attempt #{i}/10 there are running \"killed\" processes for #{id}(#{rc}): stdout: #{out} stderr: #{err}"
623
+ sleep 0.5
624
+ end
625
+
626
+ # looks backwards but 0 implies processes still existed
627
+ if 0 == rc
628
+ out,err,rc = OpenShift::Utils::ShellExec.shellCmd("ps -u #{@uid} -o state,pid,ppid,cmd")
629
+ Syslog.alert "ERROR: failed to kill all processes for #{id}(#{rc}): stdout: #{out} stderr: #{err}"
630
+ end
631
+ end
632
+
633
+ # Private: Purge IPC entities for a given gear
634
+ #
635
+ # Enumerate and remove all IPC entities for a given user ID or
636
+ # user name.
637
+ #
638
+ # Examples:
639
+ # purge_sysvipc
640
+ # => true
641
+ # ipcs -c
642
+ # ipcrm -s id
643
+ # ipcrm -m id
644
+ #
645
+ # Raises exception on error.
646
+ #
647
+ def purge_sysvipc(id)
648
+ if id.nil? or id == ""
649
+ raise ArgumentError.new("Supplied ID must be a user name or uid.")
650
+ end
651
+
652
+ ['-m', '-q', '-s' ].each do |ipctype|
653
+ out,err,rc=shellCmd(%{/usr/bin/ipcs -c #{ipctype} 2> /dev/null})
654
+ out.lines do |ipcl|
655
+ next unless ipcl=~/^\d/
656
+ ipcent = ipcl.split
657
+ if ipcent[2] == id
658
+ # The ID may already be gone
659
+ shellCmd(%{/usr/bin/ipcrm #{ipctype} #{ipcent[0]}})
660
+ end
661
+ end
662
+ end
663
+ end
664
+
665
+ # private: Write ssh authorized_keys file
666
+ #
667
+ # @param [String] authorized_keys_file ssh authorized_keys path
668
+ # @param [Hash] keys authorized keys with the comment field as the key
669
+ # @return [Hash] authorized keys with the comment field as the key
670
+ def write_ssh_keys(authorized_keys_file, keys)
671
+ File.open(authorized_keys_file,
672
+ File::WRONLY|File::TRUNC|File::CREAT,
673
+ 0o0440) do |file|
674
+ file.write(keys.values.join("\n"))
675
+ file.write("\n")
676
+ end
677
+ FileUtils.chown_R('root', @uuid, authorized_keys_file)
678
+ set_selinux_context(authorized_keys_file)
679
+
680
+ keys
681
+ end
682
+
683
+ # private: Read ssh authorized_keys file
684
+ #
685
+ # @param [String] authorized_keys_file ssh authorized_keys path
686
+ # @return [Hash] authorized keys with the comment field as the key
687
+ def read_ssh_keys(authorized_keys_file)
688
+ keys = {}
689
+ if File.exists? authorized_keys_file
690
+ File.open(authorized_keys_file, File::RDONLY).each_line do | line |
691
+ options, key_type, key, comment = line.split
692
+ keys[comment] = line.chomp
693
+ end
694
+ FileUtils.chown_R('root', @uuid, authorized_keys_file)
695
+ end
696
+ keys
697
+ end
698
+
699
+ # private: Determine the MCS label for a given uid
700
+ #
701
+ # @param [Integer] The user ID
702
+ # @return [String] The SELinux MCS label
703
+ def get_mcs_label(uid)
704
+ if ((uid.to_i < 0) || (uid.to_i>523776))
705
+ raise ArgumentError, "Supplied UID must be between 0 and 523776."
706
+ end
707
+
708
+ setsize=1023
709
+ tier=setsize
710
+ ord=uid.to_i
711
+ while ord > tier
712
+ ord -= tier
713
+ tier -= 1
714
+ end
715
+ tier = setsize - tier
716
+ "s0:c#{tier},c#{ord + tier}"
717
+ end
718
+
719
+ # private: Set the SELinux context on a file or directory
720
+ #
721
+ # @param [Integer] The user ID
722
+ def set_selinux_context(path)
723
+ mcs_label=get_mcs_label(@uid)
724
+
725
+ cmd = "restorecon -R #{path}"
726
+ out, err, rc = shellCmd(cmd)
727
+ Syslog.err(
728
+ "ERROR: unable to restorecon user homedir(#{rc}): #{cmd} stdout: #{out} stderr: #{err}"
729
+ ) unless 0 == rc
730
+ cmd = "chcon -R -l #{mcs_label} #{path}/*"
731
+
732
+ out, err, rc = shellCmd(cmd)
733
+ Syslog.err(
734
+ "ERROR: unable to chcon user homedir(#{rc}): #{cmd} stdout: #{out} stderr: #{err}"
735
+ ) unless 0 == rc
736
+ end
737
+ end
738
+ end