lib/capistrano/tasks/systemd.rake in capistrano-sidekiq-2.0.0 vs lib/capistrano/tasks/systemd.rake in capistrano-sidekiq-2.1.0

- old
+ new

@@ -1,72 +1,113 @@ git_plugin = self namespace :sidekiq do - desc 'Quiet sidekiq (stop fetching new tasks from Redis)' - task :quiet do - on roles fetch(:sidekiq_roles) do |role| - git_plugin.switch_user(role) do - if fetch(:sidekiq_service_unit_user) == :system - execute :sudo, :systemctl, "reload", fetch(:sidekiq_service_unit_name), raise_on_non_zero_exit: false - else - execute :systemctl, "--user", "reload", fetch(:sidekiq_service_unit_name), raise_on_non_zero_exit: false + + standard_actions = { + start: 'Start Sidekiq', + stop: 'Stop Sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)', + status: 'Get Sidekiq Status' + } + standard_actions.each do |command, description| + desc description + task command do + on roles fetch(:sidekiq_roles) do |role| + git_plugin.switch_user(role) do + git_plugin.systemctl_command(command) end end end end - desc 'Stop sidekiq (graceful shutdown within timeout, put unfinished tasks back to Redis)' - task :stop do + desc 'Restart Sidekiq (Quiet, Wait till workers finish or 30 seconds, Stop, Start)' + task :restart do on roles fetch(:sidekiq_roles) do |role| git_plugin.switch_user(role) do - if fetch(:sidekiq_service_unit_user) == :system - execute :sudo, :systemctl, "stop", fetch(:sidekiq_service_unit_name) - else - execute :systemctl, "--user", "stop", fetch(:sidekiq_service_unit_name) + git_plugin.quiet_sidekiq + git_plugin.process_block do |process| + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + running = nil + + # get running workers + while (running.nil? || running > 0) && git_plugin.duration(start_time) < 30 do + command_args = + if fetch(:sidekiq_service_unit_user) == :system + [:sudo, 'systemd-cgls'] + else + ['systemd-clgs', '--user'] + end + # need to pipe through tr -cd... to strip out systemd colors or you + # get log error messages for non UTF-8 characters. + command_args.push( + '-u', "#{git_plugin.sidekiq_service_unit_name(process: process)}.service", + '|', 'tr -cd \'\11\12\15\40-\176\'' + ) + status = capture(*command_args, raise_on_non_zero_exit: false) + status_match = status.match(/\[(?<running>\d+) of (?<total>\d+) busy\]/) + break unless status_match + + running = status_match[:running]&.to_i + + colors = SSHKit::Color.new($stdout) + if running.zero? + info colors.colorize("✔ Process ##{process}: No running workers. Shutting down for restart!", :green) + else + info colors.colorize("⧗ Process ##{process}: Waiting for #{running} workers.", :yellow) + sleep(1) + end + end + + git_plugin.systemctl_command(:stop, process: process) + git_plugin.systemctl_command(:start, process: process) end end end end - desc 'Start sidekiq' - task :start do + desc 'Quiet Sidekiq (stop fetching new tasks from Redis)' + task :quiet do on roles fetch(:sidekiq_roles) do |role| git_plugin.switch_user(role) do - if fetch(:sidekiq_service_unit_user) == :system - execute :sudo, :systemctl, 'start', fetch(:sidekiq_service_unit_name) - else - execute :systemctl, '--user', 'start', fetch(:sidekiq_service_unit_name) - end + git_plugin.quiet_sidekiq end end end desc 'Install systemd sidekiq service' task :install do on roles fetch(:sidekiq_roles) do |role| git_plugin.switch_user(role) do git_plugin.create_systemd_template - if fetch(:sidekiq_service_unit_user) == :system - execute :sudo, :systemctl, "enable", fetch(:sidekiq_service_unit_name) - else - execute :systemctl, "--user", "enable", fetch(:sidekiq_service_unit_name) - execute :loginctl, "enable-linger", fetch(:sidekiq_lingering_user) if fetch(:sidekiq_enable_lingering) + if git_plugin.config_per_process? + git_plugin.process_block do |process| + git_plugin.create_systemd_config_symlink(process) + end end + git_plugin.systemctl_command(:enable) + + if fetch(:sidekiq_service_unit_user) != :system && fetch(:sidekiq_enable_lingering) + execute :loginctl, "enable-linger", fetch(:sidekiq_lingering_user) + end end end end - desc 'UnInstall systemd sidekiq service' + desc 'Uninstall systemd sidekiq service' task :uninstall do on roles fetch(:sidekiq_roles) do |role| git_plugin.switch_user(role) do - if fetch(:sidekiq_service_unit_user) == :system - execute :sudo, :systemctl, "disable", fetch(:sidekiq_service_unit_name) - else - execute :systemctl, "--user", "disable", fetch(:sidekiq_service_unit_name) + git_plugin.systemctl_command(:stop) + git_plugin.systemctl_command(:disable) + if git_plugin.config_per_process? + git_plugin.process_block do |process| + git_plugin.delete_systemd_config_symlink(process) + end end - execute :rm, '-f', File.join(fetch(:service_unit_path, fetch_systemd_unit_path), fetch(:sidekiq_service_unit_name)) + execute :sudo, :rm, '-f', File.join( + fetch(:service_unit_path, git_plugin.fetch_systemd_unit_path), + git_plugin.sidekiq_service_file_name + ) end end end desc 'Generate service_locally' @@ -85,11 +126,14 @@ File.join(home_dir, ".config", "systemd", "user") end end def compiled_template + local_template_directory = fetch(:sidekiq_service_templates_path) search_paths = [ + File.join(local_template_directory, "#{fetch(:sidekiq_service_unit_name)}.service.capistrano.erb"), + File.join(local_template_directory, 'sidekiq.service.capistrano.erb'), File.expand_path( File.join(*%w[.. .. .. generators capistrano sidekiq systemd templates sidekiq.service.capistrano.erb]), __FILE__ ), ] @@ -99,27 +143,82 @@ end def create_systemd_template ctemplate = compiled_template systemd_path = fetch(:service_unit_path, fetch_systemd_unit_path) + systemd_file_name = File.join(systemd_path, sidekiq_service_file_name) if fetch(:sidekiq_service_unit_user) == :user backend.execute :mkdir, "-p", systemd_path end - backend.upload!( - StringIO.new(ctemplate), - "/tmp/#{fetch :sidekiq_service_unit_name}.service" - ) + + temp_file_name = File.join('/tmp', sidekiq_service_file_name) + backend.upload!(StringIO.new(ctemplate), temp_file_name) if fetch(:sidekiq_service_unit_user) == :system - backend.execute :sudo, :mv, "/tmp/#{fetch :sidekiq_service_unit_name}.service", "#{systemd_path}/#{fetch :sidekiq_service_unit_name}.service" + backend.execute :sudo, :mv, temp_file_name, systemd_file_name backend.execute :sudo, :systemctl, "daemon-reload" else - backend.execute :mv, "/tmp/#{fetch :sidekiq_service_unit_name}.service", "#{systemd_path}/#{fetch :sidekiq_service_unit_name}.service" + backend.execute :mv, temp_file_name, systemd_file_name backend.execute :systemctl, "--user", "daemon-reload" end end + def create_systemd_config_symlink(process) + config = fetch(:sidekiq_config) + return unless config + + process_config = config[process - 1] + if process_config.nil? + backend.error( + "No configuration for Process ##{process} found. "\ + 'Please make sure you have 1 item in :sidekiq_config for each process.' + ) + exit 1 + end + + base_path = fetch(:deploy_to) + config_link_base_path = File.join(base_path, 'shared', 'sidekiq_systemd') + config_link_path = File.join( + config_link_base_path, sidekiq_systemd_config_name(process) + ) + process_config_path = File.join(base_path, 'current', process_config) + + backend.execute :mkdir, '-p', config_link_base_path + backend.execute :ln, '-sf', process_config_path, config_link_path + end + + def delete_systemd_config_symlink(process) + config_link_path = File.join( + fetch(:deploy_to), 'shared', 'sidekiq_systemd', + sidekiq_systemd_config_name(process) + ) + backend.execute :rm, config_link_path, raise_on_non_zero_exit: false + end + + def systemctl_command(*args, process: nil) + execute_array = + if fetch(:sidekiq_service_unit_user) == :system + [:sudo, :systemctl] + else + [:systemctl, '--user'] + end + + if process + execute_array.push( + *args, sidekiq_service_unit_name(process: process) + ).flatten + backend.execute(*execute_array, raise_on_non_zero_exit: false) + else + execute_array.push(*args, sidekiq_service_unit_name).flatten + backend.execute(*execute_array, raise_on_non_zero_exit: false) + end + end + + def quiet_sidekiq + systemctl_command(:kill, '-s', :TSTP) + end + def switch_user(role) su_user = sidekiq_user if su_user != role.user yield else @@ -130,6 +229,72 @@ end def sidekiq_user fetch(:sidekiq_user, fetch(:run_as)) end + + def sidekiq_config + config = fetch(:sidekiq_config) + return unless config + + if config_per_process? + config = File.join( + fetch(:deploy_to), 'shared', 'sidekiq_systemd', + sidekiq_systemd_config_name + ) + "--config #{config}" + else + "--config #{config}" + end + end + + def sidekiq_concurrency + if fetch(:sidekiq_concurrency) + "--concurrency #{fetch(:sidekiq_concurrency)}" + end + end + + def sidekiq_processes + fetch(:sidekiq_processes, 1) + end + + def sidekiq_queues + Array(fetch(:sidekiq_queue)).map do |queue| + "--queue #{queue}" + end.join(' ') + end + + def sidekiq_service_file_name + "#{fetch(:sidekiq_service_unit_name)}@.service" + end + + def sidekiq_service_unit_name(process: nil) + if process + "#{fetch(:sidekiq_service_unit_name)}@#{process}" + else + "#{fetch(:sidekiq_service_unit_name)}@{1..#{sidekiq_processes}}" + end + end + + # process = 1 | sidekiq_systemd_1.yaml + # process = nil | sidekiq_systemd_%i.yaml + def sidekiq_systemd_config_name(process = nil) + file_name = 'sidekiq_systemd_' + file_name << (process&.to_s || '%i') + "#{file_name}.yaml" + end + + def config_per_process? + fetch(:sidekiq_config).is_a?(Array) + end + + def process_block + (1..sidekiq_processes).each do |process| + yield(process) + end + end + + def duration(start_time) + Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time + end + end