File: apache2_controller.rb

package info (click to toggle)
passenger 2.2.11debian-2
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 11,576 kB
  • ctags: 28,138
  • sloc: cpp: 66,323; ruby: 9,646; ansic: 2,425; python: 141; sh: 56; makefile: 29
file content (250 lines) | stat: -rw-r--r-- 6,631 bytes parent folder | download | duplicates (2)
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
require 'erb'
require 'fileutils'
require 'phusion_passenger/platform_info'

# A class for starting, stopping and restarting Apache, and for manipulating
# its configuration file. This is used by the integration tests.
#
# Before a test begins, the test instructs Apache2Controller to create an Apache
# configuration folder, which contains an Apache configuration file and other
# configuration resources that Apache needs. The Apache configuration file is
# created from a template (see Apache2Controller::STUB_DIR).
# The test can define configuration customizations. For example, it can tell
# Apache2Controller to add configuration options, virtual host definitions, etc.
#
# After the configuration folder has been created, Apache2Controller will start
# Apache. After Apache has been started, the test will be run. Apache2Controller
# will stop Apache after the test is done.
#
# Apache2Controller ensures that starting, stopping and restarting are not prone
# to race conditions. For example, it ensures that when #start returns, Apache
# really is listening on its server socket instead of still initializing.
#
# == Usage
#
# Suppose that you want to test a hypothetical "AlwaysPrintHelloWorld"
# Apache configuration option. Then you can write the following test:
#
#   apache = Apache2Controller.new
#   
#   # Add a configuration option to the configuration file.
#   apache << "AlwaysPrintHelloWorld on"
#   
#   # Write configuration file and start Apache with that configuration file.
#   apache.start
#   
#   begin
#       response_body = http_get("http://localhost:#{apache.port}/some/url")
#       response_body.should == "hello world!"
#   ensure
#       apache.stop
#   end
class Apache2Controller
	STUB_DIR = File.expand_path(File.dirname(__FILE__) + "/../stub/apache2")
	
	class VHost
		attr_accessor :domain
		attr_accessor :document_root
		attr_accessor :additional_configs
		
		def initialize(domain, document_root)
			@domain = domain
			@document_root = document_root
			@additional_configs = []
		end
		
		def <<(config)
			@additional_configs << config
		end
	end
	
	attr_accessor :port
	attr_accessor :vhosts
	
	def initialize(options = nil)
		set(options) if options
		@port = 64506
		@vhosts = []
		@extra = []
		@server_root = File.expand_path('tmp.apache2')
		@passenger_root = File.expand_path(File.dirname(__FILE__) + "/../..")
		@mod_passenger = File.expand_path(File.dirname(__FILE__) + "/../../ext/apache2/mod_passenger.so")
	end
	
	def set(options)
		options.each_pair do |key, value|
			instance_variable_set("@#{key}", value)
		end
	end
	
	# Create an Apache configuration folder and start Apache on that
	# configuration folder. This method does not return until Apache
	# has done initializing.
	#
	# If Apache is already started, this this method will stop Apache first.
	def start
		if running?
			stop
		else
			File.unlink("#{@server_root}/httpd.pid") rescue nil
		end
		
		if File.exist?(@server_root)
			FileUtils.rm_r(@server_root)
		end
		FileUtils.mkdir_p(@server_root)
		write_config_file
		FileUtils.cp("#{STUB_DIR}/mime.types", @server_root)
		
		if !system(PlatformInfo.httpd, "-f", "#{@server_root}/httpd.conf", "-k", "start")
			raise "Could not start an Apache server."
		end
		
		begin
			# Wait until the PID file has been created.
			Timeout::timeout(20) do
				while !File.exist?("#{@server_root}/httpd.pid")
					sleep(0.1)
				end
			end
			# Wait until Apache is listening on the server port.
			Timeout::timeout(7) do
				done = false
				while !done
					begin
						socket = TCPSocket.new('localhost', @port)
						socket.close
						done = true
					rescue Errno::ECONNREFUSED
						sleep(0.1)
					end
				end
			end
		rescue Timeout::Error
			raise "Could not start an Apache server."
		end
		File.chmod(0666, *Dir["#{@server_root}/*"]) rescue nil
	end
	
	def graceful_restart
		write_config_file
		if !system(PlatformInfo.httpd, "-f", "#{@server_root}/httpd.conf", "-k", "graceful")
			raise "Cannot restart Apache."
		end
	end
	
	# Stop Apache and delete its configuration folder. This method waits
	# until Apache is done with its shutdown procedure.
	#
	# This method does nothing if Apache is already stopped.
	def stop
		pid_file = "#{@server_root}/httpd.pid"
		if File.exist?(pid_file)
			begin
				pid = File.read(pid_file).strip.to_i
				Process.kill('SIGTERM', pid)
			rescue Errno::ESRCH
				# Looks like a stale pid file.
				FileUtils.rm_r(@server_root)
				return
			end
		end
		begin
			# Wait until the PID file is removed.
			Timeout::timeout(17) do
				while File.exist?(pid_file)
					sleep(0.1)
				end
			end
			# Wait until the server socket is closed.
			Timeout::timeout(7) do
				done = false
				while !done
					begin
						socket = TCPSocket.new('localhost', @port)
						socket.close
						sleep(0.1)
					rescue SystemCallError
						done = true
					end
				end
			end
		rescue Timeout::Error
			raise "Unable to stop Apache."
		end
		if File.exist?(@server_root)
			FileUtils.rm_r(@server_root)
		end
	end
	
	# Define a virtual host configuration block for the Apache configuration
	# file. If there was already a vhost definition with the same domain name,
	# then it will be overwritten.
	#
	# The given document root will be created if it doesn't exist.
	def set_vhost(domain, document_root)
		FileUtils.mkdir_p(document_root)
		vhost = VHost.new(domain, document_root)
		if block_given?
			yield vhost
		end
		vhosts.reject! {|host| host.domain == domain}
		vhosts << vhost
	end
	
	# Checks whether this Apache instance is running.
	def running?
		if File.exist?("#{@server_root}/httpd.pid")
			pid = File.read("#{@server_root}/httpd.pid").strip
			begin
				Process.kill(0, pid.to_i)
				return true
			rescue Errno::ESRCH
				return false
			rescue SystemCallError
				return true
			end
		else
			return false
		end
	end
	
	# Defines a configuration snippet to be added to the Apache configuration file.
	def <<(line)
		@extra << line
	end

private
	def get_binding
		return binding
	end
	
	def write_config_file
		template = ERB.new(File.read("#{STUB_DIR}/httpd.conf.erb"))
		File.open("#{@server_root}/httpd.conf", 'w') do |f|
			f.write(template.result(get_binding))
		end
	end
	
	def modules_dir
		@@modules_dir ||= `#{PlatformInfo.apxs2} -q LIBEXECDIR`.strip
	end
	
	def builtin_modules
		@@builtin_modules ||= `#{PlatformInfo.httpd} -l`.split("\n").grep(/\.c$/).map do |line|
			line.strip
		end
	end
	
	def has_builtin_module?(name)
		return builtin_modules.include?(name)
	end
	
	def has_module?(name)
		return File.exist?("#{modules_dir}/#{name}")
	end
	
	def we_are_root?
		return Process.uid == 0
	end
end