From cd4dc311ae27bc36534d8a176675b699de25bc93 Mon Sep 17 00:00:00 2001 From: Muhammad Nuzaihan Date: Fri, 16 Mar 2018 02:21:04 +0800 Subject: [PATCH 1/2] Refactor and test IPv6 support in websocket server --- script/websocket-server.rb | 36 ++++++++++++------------- spec/scripts/websocket_server_spec.rb | 39 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 spec/scripts/websocket_server_spec.rb diff --git a/script/websocket-server.rb b/script/websocket-server.rb index e701ec528..cdee2cb15 100755 --- a/script/websocket-server.rb +++ b/script/websocket-server.rb @@ -5,7 +5,7 @@ $LOAD_PATH << './lib' require 'rubygems' # load rails env -dir = File.expand_path(File.join(File.dirname(__FILE__), '..')) +dir = File.expand_path('..', __dir__) Dir.chdir dir RAILS_ENV = ENV['RAILS_ENV'] || 'development' @@ -38,8 +38,8 @@ def after_fork(dir) file.sync = true end - $stdout.reopen( "#{dir}/log/websocket-server_out.log", 'w') - $stderr.reopen( "#{dir}/log/websocket-server_err.log", 'w') + $stdout.reopen( "#{dir}/log/websocket-server_out.log", 'w').sync = true + $stderr.reopen( "#{dir}/log/websocket-server_err.log", 'w').sync = true end before_fork @@ -70,7 +70,7 @@ OptionParser.new do |opts| @options[:p] = p end opts.on('-b', '--bind [OPT]', 'bind address') do |b| - @options[:b] = b + @options[:b] = IPAddr.new(b).to_s end opts.on('-s', '--secure', 'enable secure connections') do |s| @options[:s] = s @@ -92,29 +92,29 @@ if ARGV[0] != 'start' && ARGV[0] != 'stop' end if ARGV[0] == 'stop' - puts "Stopping websocket server (pid:#{@options[:i]})" + pid = File.read(@options[:i]).to_i + puts "Stopping websocket server (pid: #{pid})" - # read pid - pid = File.open( @options[:i].to_s ).read - pid.gsub!(/\r|\n/, '') + # IMPORTANT: Use SIGTERM (15), not SIGKILL (9) + # Daemons.rb cleans up the PID file automatically on termination; + # SIGKILL ends the process immediately and bypasses cleanup. + # See https://major.io/2010/03/18/sigterm-vs-sigkill/ for more. + Process.kill(:SIGTERM, pid) - # kill - Process.kill( 9, pid.to_i ) exit end if ARGV[0] == 'start' && @options[:d] - puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure:#{@options[:s]},pid:#{@options[:i]})" + puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure: #{@options[:s]}, pidfile: #{@options[:i]})" - Daemons.daemonize + # Use Daemons.rb's built-in facility for generating PID files + Daemons.daemonize( + app_name: File.basename(@options[:i], '.pid'), + dir_mode: :normal, + dir: File.dirname(@options[:i]) + ) after_fork(dir) - - # create pid file - daemon_pid = File.new(@options[:i].to_s, 'w') - daemon_pid.sync = true - daemon_pid.puts(Process.pid.to_s) - daemon_pid.close end @clients = {} diff --git a/spec/scripts/websocket_server_spec.rb b/spec/scripts/websocket_server_spec.rb new file mode 100644 index 000000000..557fcab70 --- /dev/null +++ b/spec/scripts/websocket_server_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require 'timeout' + +describe 'websocket-server' do + # Why not Rails.root.join here? + # Because it's not avaialable in this spec (no 'rails_helper' = faster start-up) + let(:app_root) { File.expand_path('../..', __dir__) } + let(:ws_server) { File.expand_path('script/websocket-server.rb', app_root) } + let(:pidfile) { File.expand_path('tmp/pids/websocket.pid', app_root) } + let(:output_log) { File.expand_path('log/websocket-server_out.log', app_root) } + let(:error_log) { File.expand_path('log/websocket-server_err.log', app_root) } + + context 'with IPv6 bind address (via -b option)' do + # This error is raised for invalid bind addresses + let(:error_msg) { "`start_tcp_server': no acceptor" } + let(:ipv6_addr) { '::1/128' } + + # Flush logs + before do + File.write(output_log, '') + File.write(error_log, '') + end + + it 'starts up successfully' do + begin + system("#{ws_server} start -db #{ipv6_addr} >/dev/null 2>&1") + + # Wait for daemon to start + Timeout.timeout(20, Timeout::Error, 'WebSocket Server startup timed out') do + loop { break if File.size(output_log) + File.size(error_log) > 0 } + end + + expect(File.read(error_log)).not_to include(error_msg) + ensure + system("#{ws_server} stop >/dev/null 2>&1") if File.exist?(pidfile) + end + end + end +end From 297d9bb044fb927ee9762fb9991d39c252ec17df Mon Sep 17 00:00:00 2001 From: Ryan Lue Date: Fri, 4 May 2018 13:40:42 +0800 Subject: [PATCH 2/2] Clean up database.yml files --- config/database.yml.test-mysql | 11 ----- config/database.yml.test-postgresql | 11 ----- contrib/database.yml | 30 +++++++++++++ {config => contrib}/database.yml.pkgr | 0 lib/tasks/bootstrap.rake | 63 ++++++++++++++++++--------- 5 files changed, 73 insertions(+), 42 deletions(-) delete mode 100644 config/database.yml.test-mysql delete mode 100644 config/database.yml.test-postgresql create mode 100644 contrib/database.yml rename {config => contrib}/database.yml.pkgr (100%) diff --git a/config/database.yml.test-mysql b/config/database.yml.test-mysql deleted file mode 100644 index f6b525b4c..000000000 --- a/config/database.yml.test-mysql +++ /dev/null @@ -1,11 +0,0 @@ -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: mysql2 - database: zammad_test - pool: 50 - timeout: 5000 - encoding: utf8 - username: some_user - password: some_pass diff --git a/config/database.yml.test-postgresql b/config/database.yml.test-postgresql deleted file mode 100644 index bda094235..000000000 --- a/config/database.yml.test-postgresql +++ /dev/null @@ -1,11 +0,0 @@ -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: postgresql - database: zammad_test - pool: 50 - timeout: 5000 - encoding: utf8 - username: postgres - password: diff --git a/contrib/database.yml b/contrib/database.yml new file mode 100644 index 000000000..c3c4a6d33 --- /dev/null +++ b/contrib/database.yml @@ -0,0 +1,30 @@ +default: &default + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: 50 + timeout: 5000 + encoding: utf8 + # postgresql ----------------------------------------------------------------- + adapter: postgresql + # username: + # password: + # mysql ---------------------------------------------------------------------- + # adapter: mysql2 + # host: localhost + # username: + # password: + +production: + <<: *default + database: zammad_prod + +development: + <<: *default + database: zammad_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: zammad_test diff --git a/config/database.yml.pkgr b/contrib/database.yml.pkgr similarity index 100% rename from config/database.yml.pkgr rename to contrib/database.yml.pkgr diff --git a/lib/tasks/bootstrap.rake b/lib/tasks/bootstrap.rake index 7a6487152..41e63d6c9 100644 --- a/lib/tasks/bootstrap.rake +++ b/lib/tasks/bootstrap.rake @@ -1,31 +1,54 @@ +module BootstrapRakeHelper + APP_CACHE = Dir.glob(Rails.root.join('tmp', 'cache*')) + SERVER_LOG = Rails.root.join('log', "#{Rails.env}.log") + AUTO_WIZARD = { source: Rails.root.join('contrib', 'auto_wizard_test.json'), + dest: Rails.root.join('auto_wizard.json') }.freeze + DB_CONFIG = { source: Rails.root.join('contrib', 'database.yml'), + dest: Rails.root.join('config', 'database.yml') }.freeze + + def flush_cache_and_logs + FileUtils.rm_rf(APP_CACHE) + File.write(SERVER_LOG, '') + end + + def run_auto_wizard + FileUtils.ln(AUTO_WIZARD[:source], AUTO_WIZARD[:dest], force: true) + AutoWizard.setup + + # set system init to done + UserInfo.current_user_id = 1 + Setting.set('system_init_done', true) + end + + def add_database_config + raise Errno::ENOENT, 'contrib/database.yml not found' unless File.exist?(DB_CONFIG[:source]) + + if File.exist?(DB_CONFIG[:dest]) + return if FileUtils.identical?(DB_CONFIG[:source], DB_CONFIG[:dest]) + printf 'config/database.yml: File exists. Overwrite? [Y/n] ' + return if STDIN.gets.chomp.downcase == 'n' + end + + FileUtils.cp(DB_CONFIG[:source], DB_CONFIG[:dest]) + end +end + namespace :bs do desc 'Bootstrap the application' - task :init => %i[db:create db:migrate db:seed] do + task :init => %i[db_config db:create db:migrate db:seed] do + include BootstrapRakeHelper run_auto_wizard end desc 'Reset the application to its initial state' task :reset => %i[db:reset] do + include BootstrapRakeHelper run_auto_wizard flush_cache_and_logs end -end - -APP_CACHE = Dir.glob(Rails.root.join('tmp', 'cache*')) -SERVER_LOG = Rails.root.join('log', "#{Rails.env}.log") -AUTO_WIZARD = { source: Rails.root.join('contrib', 'auto_wizard_test.json'), - dest: Rails.root.join('auto_wizard.json') }.freeze - -def flush_cache_and_logs - FileUtils.rm_rf(APP_CACHE) - File.write(SERVER_LOG, '') -end - -def run_auto_wizard - FileUtils.ln(AUTO_WIZARD[:source], AUTO_WIZARD[:dest], force: true) - AutoWizard.setup - - # set system init to done - UserInfo.current_user_id = 1 - Setting.set('system_init_done', true) + + task :db_config do + include BootstrapRakeHelper + add_database_config + end end