Skip to content

Commit 245f493

Browse files
Copilotdamacus
andauthored
feat(service): Allow configuration of systemd service Restart directive (#813)
* Add restart_policy and restart_sec properties to elasticsearch_service Co-authored-by: damacus <[email protected]> * Add documentation and integration tests for restart functionality Co-authored-by: damacus <[email protected]> * Fix RSpec failure by using proper test recipe instead of converge_dsl Co-authored-by: damacus <[email protected]> * Revert to inline testing with converge_block method Co-authored-by: damacus <[email protected]> * Fixes Signed-off-by: Dan Webb <[email protected]> --------- Signed-off-by: Dan Webb <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: damacus <[email protected]> Co-authored-by: Dan Webb <[email protected]>
1 parent b4c63d3 commit 245f493

File tree

8 files changed

+199
-75
lines changed

8 files changed

+199
-75
lines changed

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require:
2+
- cookstyle

Dangerfile

Lines changed: 0 additions & 47 deletions
This file was deleted.

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,37 @@ a matching `elasticsearch_config` resource in the collection.
232232
elasticsearch_service 'elasticsearch'
233233
```
234234

235+
#### Restart Configuration
236+
237+
You can configure systemd restart behavior using the `restart_policy` and `restart_sec` properties:
238+
239+
```ruby
240+
elasticsearch_service 'elasticsearch' do
241+
restart_policy 'on-failure' # Restart only on failure
242+
restart_sec 30 # Wait 30 seconds before restart
243+
end
244+
```
245+
246+
```ruby
247+
elasticsearch_service 'elasticsearch' do
248+
restart_policy 'always' # Always restart
249+
restart_sec '5min' # Wait 5 minutes before restart
250+
end
251+
```
252+
253+
Valid restart policies:
254+
255+
- `''` (empty string) - No automatic restart (default, maintains backward compatibility)
256+
- `'no'` - Never restart
257+
- `'always'` - Always restart regardless of exit status
258+
- `'on-success'` - Restart only when process exits cleanly
259+
- `'on-failure'` - Restart when process exits with non-zero code, killed by signal, timeout, or watchdog
260+
- `'on-abnormal'` - Restart when process is terminated by signal, timeout, or watchdog
261+
- `'on-abort'` - Restart when process exits due to uncaught signal
262+
- `'on-watchdog'` - Restart when watchdog timeout occurs
263+
264+
The `restart_sec` property accepts either an integer (seconds) or a systemd time span string (e.g., "5min", "30s", "1m 30s").
265+
235266
If you'd like to skip init scripts and systemd scripts, simply pass `nil` for
236267
the template file (init_source or systemd_source) and this cookbook will
237268
entirely skip trying to setup those scripts. Combined with changing the default

mise.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# .mise.toml
2+
3+
[env]
4+
PATH = "/opt/chef-workstation/bin:/opt/chef-workstation/embedded/bin:{{env.PATH}}"
5+
KITCHEN_LOCAL_YAML = "kitchen.dokken.yml"

resources/service.rb

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
[Symbol, String, Array],
1515
default: [:enable, :start]
1616

17+
property :restart_policy,
18+
String,
19+
default: '',
20+
regex: /^(no|always|on-success|on-failure|on-abnormal|on-abort|on-watchdog)?$/,
21+
description: 'Systemd restart policy. Valid values: no, always, on-success, on-failure, on-abnormal, on-abort, on-watchdog. Defaults to empty string (no restart).'
22+
23+
property :restart_sec,
24+
[Integer, String],
25+
description: 'Configures the time to sleep before restarting a service (seconds). Takes an integer or time span value (e.g., "5min 20s").'
26+
1727
action :configure do
1828
es_user = find_es_resource(Chef.run_context, :elasticsearch_user, new_resource)
1929
es_conf = find_es_resource(Chef.run_context, :elasticsearch_configure, new_resource)
@@ -30,6 +40,38 @@
3040

3141
default_conf_dir = platform_family?('rhel', 'amazon') ? '/etc/sysconfig' : '/etc/default'
3242

43+
service_config = {
44+
Type: 'notify',
45+
RuntimeDirectory: 'elasticsearch',
46+
PrivateTmp: 'true',
47+
Environment: [
48+
"ES_HOME=#{es_conf.path_home}",
49+
'ES_PATH_CONF=/etc/elasticsearch',
50+
"PID_DIR=#{es_conf.path_pid}",
51+
'ES_SD_NOTIFY=true',
52+
],
53+
EnvironmentFile: "-#{default_conf_dir}/#{new_resource.service_name}",
54+
WorkingDirectory: es_conf.path_home.to_s,
55+
User: es_user.username,
56+
Group: es_user.groupname,
57+
ExecStart: "#{es_conf.path_home}/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet",
58+
StandardOutput: 'journal',
59+
StandardError: 'inherit',
60+
LimitNOFILE: '65535',
61+
LimitNPROC: '4096',
62+
LimitAS: 'infinity',
63+
LimitFSIZE: 'infinity',
64+
TimeoutStopSec: '0',
65+
KillSignal: 'SIGTERM',
66+
KillMode: 'process',
67+
SendSIGKILL: 'no',
68+
SuccessExitStatus: '143',
69+
TimeoutStartSec: '900',
70+
}
71+
72+
service_config[:Restart] = new_resource.restart_policy if new_resource.restart_policy && !new_resource.restart_policy.empty?
73+
service_config[:RestartSec] = new_resource.restart_sec if new_resource.restart_sec
74+
3375
systemd_unit new_resource.service_name do
3476
content(
3577
Unit: {
@@ -38,34 +80,7 @@
3880
Wants: 'network-online.target',
3981
After: 'network-online.target',
4082
},
41-
Service: {
42-
Type: 'notify',
43-
RuntimeDirectory: 'elasticsearch',
44-
PrivateTmp: 'true',
45-
Environment: [
46-
"ES_HOME=#{es_conf.path_home}",
47-
'ES_PATH_CONF=/etc/elasticsearch',
48-
"PID_DIR=#{es_conf.path_pid}",
49-
'ES_SD_NOTIFY=true',
50-
],
51-
EnvironmentFile: "-#{default_conf_dir}/#{new_resource.service_name}",
52-
WorkingDirectory: es_conf.path_home.to_s,
53-
User: es_user.username,
54-
Group: es_user.groupname,
55-
ExecStart: "#{es_conf.path_home}/bin/systemd-entrypoint -p ${PID_DIR}/elasticsearch.pid --quiet",
56-
StandardOutput: 'journal',
57-
StandardError: 'inherit',
58-
LimitNOFILE: '65535',
59-
LimitNPROC: '4096',
60-
LimitAS: 'infinity',
61-
LimitFSIZE: 'infinity',
62-
TimeoutStopSec: '0',
63-
KillSignal: 'SIGTERM',
64-
KillMode: 'process',
65-
SendSIGKILL: 'no',
66-
SuccessExitStatus: '143',
67-
TimeoutStartSec: '900',
68-
},
83+
Service: service_config,
6984
Install: {
7085
WantedBy: 'multi-user.target',
7186
}

spec/service_spec.rb

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require_relative 'spec_helper'
2+
3+
describe 'elasticsearch_service' do
4+
before { stub_resources }
5+
6+
supported_platforms.each do |platform, versions|
7+
versions.each do |version|
8+
context "on #{platform.capitalize} #{version}" do
9+
let(:chef_run) do
10+
ChefSpec::ServerRunner.new(platform: platform, version: version, step_into: ['elasticsearch_service']) do |node, server|
11+
node_resources(node)
12+
stub_chef_zero(platform, version, server)
13+
end.converge('test::restart_policy')
14+
end
15+
16+
it 'creates systemd unit with restart policy' do
17+
expect(chef_run).to create_systemd_unit('elasticsearch').with(
18+
content: hash_including(
19+
Service: hash_including(
20+
Restart: 'on-failure',
21+
RestartSec: 30
22+
)
23+
)
24+
)
25+
end
26+
27+
it 'enables and starts the elasticsearch service' do
28+
expect(chef_run).to enable_service('elasticsearch')
29+
expect(chef_run).to start_service('elasticsearch')
30+
end
31+
end
32+
end
33+
end
34+
35+
# Test default behavior (no restart)
36+
# rubocop:disable Style/MultilineBlockChain
37+
context 'with default configuration' do
38+
let(:chef_run) do
39+
ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '20.04', step_into: ['elasticsearch_service']) do |node, server|
40+
node_resources(node)
41+
stub_chef_zero('ubuntu', '20.04', server)
42+
end.converge_block do
43+
elasticsearch_user 'elasticsearch'
44+
elasticsearch_install 'elasticsearch' do
45+
type 'package'
46+
end
47+
elasticsearch_configure 'elasticsearch'
48+
elasticsearch_service 'elasticsearch'
49+
end
50+
end
51+
52+
it 'creates systemd unit without restart policy' do
53+
systemd_unit = chef_run.systemd_unit('elasticsearch')
54+
expect(systemd_unit.content[:Service]).not_to have_key(:Restart)
55+
expect(systemd_unit.content[:Service]).not_to have_key(:RestartSec)
56+
end
57+
end
58+
# rubocop:enable Style/MultilineBlockChain
59+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# test fixture to validate restart_policy functionality
2+
3+
# Basic installation and user setup
4+
elasticsearch_user 'elasticsearch'
5+
6+
elasticsearch_install 'elasticsearch' do
7+
type 'package'
8+
end
9+
10+
elasticsearch_configure 'elasticsearch' do
11+
allocated_memory '256m'
12+
configuration('node.name' => 'restart_test_node')
13+
action :manage
14+
end
15+
16+
# Test service with restart policy
17+
elasticsearch_service 'elasticsearch' do
18+
restart_policy 'on-failure'
19+
restart_sec 30
20+
service_actions [:enable, :start]
21+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
control 'Service restart configuration' do
2+
impact 1.0
3+
title 'Elasticsearch service restart policy configuration'
4+
desc 'Verify that restart policy is correctly configured in systemd unit'
5+
6+
# Test that the systemd service file exists
7+
describe file('/etc/systemd/system/elasticsearch.service') do
8+
it { should exist }
9+
end
10+
11+
# Test restart policy configuration if the test recipe was used
12+
if file('/etc/systemd/system/elasticsearch.service').exist?
13+
service_content = file('/etc/systemd/system/elasticsearch.service').content
14+
15+
# Check if this is the restart_policy test
16+
if service_content.include?('Restart=on-failure')
17+
describe 'Elasticsearch service restart configuration' do
18+
it 'should have restart policy configured' do
19+
expect(service_content).to match(/^Restart=on-failure$/)
20+
end
21+
22+
it 'should have restart delay configured' do
23+
expect(service_content).to match(/^RestartSec=30$/)
24+
end
25+
end
26+
else
27+
describe 'Elasticsearch service default configuration' do
28+
it 'should not have restart policy by default' do
29+
expect(service_content).not_to match(/^Restart=/)
30+
end
31+
32+
it 'should not have restart delay by default' do
33+
expect(service_content).not_to match(/^RestartSec=/)
34+
end
35+
end
36+
end
37+
end
38+
end

0 commit comments

Comments
 (0)