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
|