diff --git a/README.md b/README.md index e62e9c8cf..fedf891a7 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,36 @@ a matching `elasticsearch_config` resource in the collection. elasticsearch_service 'elasticsearch' ``` +**Restart Configuration** + +You can configure systemd restart behavior using the `restart_policy` and `restart_sec` properties: + +```ruby +elasticsearch_service 'elasticsearch' do + restart_policy 'on-failure' # Restart only on failure + restart_sec 30 # Wait 30 seconds before restart +end +``` + +```ruby +elasticsearch_service 'elasticsearch' do + restart_policy 'always' # Always restart + restart_sec '5min' # Wait 5 minutes before restart +end +``` + +Valid restart policies: +- `''` (empty string) - No automatic restart (default, maintains backward compatibility) +- `'no'` - Never restart +- `'always'` - Always restart regardless of exit status +- `'on-success'` - Restart only when process exits cleanly +- `'on-failure'` - Restart when process exits with non-zero code, killed by signal, timeout, or watchdog +- `'on-abnormal'` - Restart when process is terminated by signal, timeout, or watchdog +- `'on-abort'` - Restart when process exits due to uncaught signal +- `'on-watchdog'` - Restart when watchdog timeout occurs + +The `restart_sec` property accepts either an integer (seconds) or a systemd time span string (e.g., "5min", "30s", "1m 30s"). + If you'd like to skip init scripts and systemd scripts, simply pass `nil` for the template file (init_source or systemd_source) and this cookbook will entirely skip trying to setup those scripts. Combined with changing the default diff --git a/resources/service.rb b/resources/service.rb index 207f92f8f..77a8c98b9 100644 --- a/resources/service.rb +++ b/resources/service.rb @@ -14,6 +14,16 @@ [Symbol, String, Array], default: [:enable, :start] +property :restart_policy, + String, + default: '', + regex: /^(no|always|on-success|on-failure|on-abnormal|on-abort|on-watchdog)?$/, + description: 'Systemd restart policy. Valid values: no, always, on-success, on-failure, on-abnormal, on-abort, on-watchdog. Defaults to empty string (no restart).' + +property :restart_sec, + [Integer, String], + description: 'Configures the time to sleep before restarting a service (seconds). Takes an integer or time span value (e.g., "5min 20s").' + action :configure do es_user = find_es_resource(Chef.run_context, :elasticsearch_user, new_resource) es_conf = find_es_resource(Chef.run_context, :elasticsearch_configure, new_resource) @@ -30,6 +40,46 @@ default_conf_dir = platform_family?('rhel', 'amazon') ? '/etc/sysconfig' : '/etc/default' + # Build service configuration with optional restart settings + service_config = { + Type: 'notify', + RuntimeDirectory: 'elasticsearch', + PrivateTmp: 'true', + Environment: [ + "ES_HOME=#{es_conf.path_home}", + 'ES_PATH_CONF=/etc/elasticsearch', + "PID_DIR=#{es_conf.path_pid}", + 'ES_SD_NOTIFY=true', + ], + EnvironmentFile: "-#{default_conf_dir}/#{new_resource.service_name}", + WorkingDirectory: "#{es_conf.path_home}", + User: es_user.username, + Group: es_user.groupname, + ExecStart: "#{es_conf.path_home}/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet", + StandardOutput: 'journal', + StandardError: 'inherit', + LimitNOFILE: '65535', + LimitNPROC: '4096', + LimitAS: 'infinity', + LimitFSIZE: 'infinity', + TimeoutStopSec: '0', + KillSignal: 'SIGTERM', + KillMode: 'process', + SendSIGKILL: 'no', + SuccessExitStatus: '143', + TimeoutStartSec: '900', + } + + # Add restart policy if specified + unless new_resource.restart_policy.empty? + service_config[:Restart] = new_resource.restart_policy + end + + # Add restart delay if specified + if new_resource.restart_sec + service_config[:RestartSec] = new_resource.restart_sec + end + systemd_unit new_resource.service_name do content( Unit: { @@ -38,34 +88,7 @@ Wants: 'network-online.target', After: 'network-online.target', }, - Service: { - Type: 'notify', - RuntimeDirectory: 'elasticsearch', - PrivateTmp: 'true', - Environment: [ - "ES_HOME=#{es_conf.path_home}", - 'ES_PATH_CONF=/etc/elasticsearch', - "PID_DIR=#{es_conf.path_pid}", - 'ES_SD_NOTIFY=true', - ], - EnvironmentFile: "-#{default_conf_dir}/#{new_resource.service_name}", - WorkingDirectory: "#{es_conf.path_home}", - User: es_user.username, - Group: es_user.groupname, - ExecStart: "#{es_conf.path_home}/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet", - StandardOutput: 'journal', - StandardError: 'inherit', - LimitNOFILE: '65535', - LimitNPROC: '4096', - LimitAS: 'infinity', - LimitFSIZE: 'infinity', - TimeoutStopSec: '0', - KillSignal: 'SIGTERM', - KillMode: 'process', - SendSIGKILL: 'no', - SuccessExitStatus: '143', - TimeoutStartSec: '900', - }, + Service: service_config, Install: { WantedBy: 'multi-user.target', } diff --git a/spec/service_spec.rb b/spec/service_spec.rb new file mode 100644 index 000000000..ace681bcc --- /dev/null +++ b/spec/service_spec.rb @@ -0,0 +1,57 @@ +require_relative 'spec_helper' + +describe 'elasticsearch_service' do + before { stub_resources } + + supported_platforms.each do |platform, versions| + versions.each do |version| + context "on #{platform.capitalize} #{version}" do + let(:chef_run) do + ChefSpec::ServerRunner.new(platform: platform, version: version, step_into: ['elasticsearch_service']) do |node, server| + node_resources(node) + stub_chef_zero(platform, version, server) + end.converge('test::restart_policy') + end + + it 'creates systemd unit with restart policy' do + expect(chef_run).to create_systemd_unit('elasticsearch').with( + content: hash_including( + Service: hash_including( + Restart: 'on-failure', + RestartSec: 30 + ) + ) + ) + end + + it 'enables and starts the elasticsearch service' do + expect(chef_run).to enable_service('elasticsearch') + expect(chef_run).to start_service('elasticsearch') + end + end + end + end + + # Test default behavior (no restart) + context 'with default configuration' do + let(:chef_run) do + ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '20.04', step_into: ['elasticsearch_service']) do |node, server| + node_resources(node) + stub_chef_zero('ubuntu', '20.04', server) + end.converge_dsl('test') do + elasticsearch_user 'elasticsearch' + elasticsearch_install 'elasticsearch' do + type 'package' + end + elasticsearch_configure 'elasticsearch' + elasticsearch_service 'elasticsearch' + end + end + + it 'creates systemd unit without restart policy' do + systemd_unit = chef_run.systemd_unit('elasticsearch') + expect(systemd_unit.content[:Service]).not_to have_key(:Restart) + expect(systemd_unit.content[:Service]).not_to have_key(:RestartSec) + end + end +end \ No newline at end of file diff --git a/test/fixtures/cookbooks/test/recipes/restart_policy.rb b/test/fixtures/cookbooks/test/recipes/restart_policy.rb new file mode 100644 index 000000000..ef5be9f90 --- /dev/null +++ b/test/fixtures/cookbooks/test/recipes/restart_policy.rb @@ -0,0 +1,21 @@ +# test fixture to validate restart_policy functionality + +# Basic installation and user setup +elasticsearch_user 'elasticsearch' + +elasticsearch_install 'elasticsearch' do + type 'package' +end + +elasticsearch_configure 'elasticsearch' do + allocated_memory '256m' + configuration('node.name' => 'restart_test_node') + action :manage +end + +# Test service with restart policy +elasticsearch_service 'elasticsearch' do + restart_policy 'on-failure' + restart_sec 30 + service_actions [:enable, :start] +end \ No newline at end of file diff --git a/test/integration/default/controls/restart_policy_spec.rb b/test/integration/default/controls/restart_policy_spec.rb new file mode 100644 index 000000000..468b342a2 --- /dev/null +++ b/test/integration/default/controls/restart_policy_spec.rb @@ -0,0 +1,38 @@ +control 'Service restart configuration' do + impact 1.0 + title 'Elasticsearch service restart policy configuration' + desc 'Verify that restart policy is correctly configured in systemd unit' + + # Test that the systemd service file exists + describe file('/etc/systemd/system/elasticsearch.service') do + it { should exist } + end + + # Test restart policy configuration if the test recipe was used + if file('/etc/systemd/system/elasticsearch.service').exist? + service_content = file('/etc/systemd/system/elasticsearch.service').content + + # Check if this is the restart_policy test + if service_content.include?('Restart=on-failure') + describe 'Elasticsearch service restart configuration' do + it 'should have restart policy configured' do + expect(service_content).to match(/^Restart=on-failure$/) + end + + it 'should have restart delay configured' do + expect(service_content).to match(/^RestartSec=30$/) + end + end + else + describe 'Elasticsearch service default configuration' do + it 'should not have restart policy by default' do + expect(service_content).not_to match(/^Restart=/) + end + + it 'should not have restart delay by default' do + expect(service_content).not_to match(/^RestartSec=/) + end + end + end + end +end \ No newline at end of file