diff --git a/REFERENCE.md b/REFERENCE.md index d38a01ba..98a7b939 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -65,6 +65,7 @@ * [`Systemd::Unit::Path`](#Systemd--Unit--Path): Possible keys for the [Path] section of a unit file * [`Systemd::Unit::Service`](#Systemd--Unit--Service): Possible keys for the [Service] section of a unit file * [`Systemd::Unit::Service::Exec`](#Systemd--Unit--Service--Exec): Possible strings for ExecStart, ExecStartPrep, ... +* [`Systemd::Unit::Slice`](#Systemd--Unit--Slice): Possible keys for the [Slice] section of a unit file * [`Systemd::Unit::Socket`](#Systemd--Unit--Socket): Possible keys for the [Socket] section of a unit file * [`Systemd::Unit::Timer`](#Systemd--Unit--Timer): Possible keys for the [Timer] section of a unit file * [`Systemd::Unit::Unit`](#Systemd--Unit--Unit): Possible keys for the [Unit] section of a unit file @@ -849,6 +850,18 @@ systemd::manage_dropin { 'user-aklog.conf': } ``` +##### set memory limits on the user slices + +```puppet +systemd::manage_dropin { 'userlimits.conf': + unit => 'user-.slice', + slice_entry => { + MemoryMax => '10G', + MemoryAccounting => true, + } +} +``` + #### Parameters The following parameters are available in the `systemd::manage_dropin` defined type: @@ -865,6 +878,7 @@ The following parameters are available in the `systemd::manage_dropin` defined t * [`notify_service`](#-systemd--manage_dropin--notify_service) * [`daemon_reload`](#-systemd--manage_dropin--daemon_reload) * [`unit_entry`](#-systemd--manage_dropin--unit_entry) +* [`slice_entry`](#-systemd--manage_dropin--slice_entry) * [`service_entry`](#-systemd--manage_dropin--service_entry) * [`install_entry`](#-systemd--manage_dropin--install_entry) * [`timer_entry`](#-systemd--manage_dropin--timer_entry) @@ -965,6 +979,14 @@ key value pairs for [Unit] section of the unit file Default value: `undef` +##### `slice_entry` + +Data type: `Optional[Systemd::Unit::Slice]` + +key value pairs for [Slice] section of the unit file + +Default value: `undef` + ##### `service_entry` Data type: `Optional[Systemd::Unit::Service]` @@ -1108,6 +1130,7 @@ The following parameters are available in the `systemd::manage_unit` defined typ * [`service_parameters`](#-systemd--manage_unit--service_parameters) * [`daemon_reload`](#-systemd--manage_unit--daemon_reload) * [`unit_entry`](#-systemd--manage_unit--unit_entry) +* [`slice_entry`](#-systemd--manage_unit--slice_entry) * [`service_entry`](#-systemd--manage_unit--service_entry) * [`install_entry`](#-systemd--manage_unit--install_entry) * [`timer_entry`](#-systemd--manage_unit--timer_entry) @@ -1224,6 +1247,14 @@ key value pairs for [Unit] section of the unit file. Default value: `undef` +##### `slice_entry` + +Data type: `Optional[Systemd::Unit::Slice]` + +key value pairs for [Slice] section of the unit file + +Default value: `undef` + ##### `service_entry` Data type: `Optional[Systemd::Unit::Service]` @@ -2495,6 +2526,46 @@ Possible strings for ExecStart, ExecStartPrep, ... Alias of `Variant[Enum[''], Pattern[/^[@\-:]*(\+|!|!!)?[@\-:]*\/.*/], Pattern[/^[@\-:]*(\+|!|!!)?[@\-:]*[^\/]*(\s.*)?$/]]` +### `Systemd::Unit::Slice` + +Possible keys for the [Slice] section of a unit file + +* **See also** + * https://www.freedesktop.org/software/systemd/man/systemd.slice.html + * https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html + +Alias of + +```puppet +Struct[{ + Optional['CPUAccounting'] => Boolean, + Optional['CPUQuota'] => Pattern['^([1-9][0-9]*)%$'], + Optional['CPUShares'] => Integer[2,262144], + Optional['Delegate'] => Boolean, + Optional['DeviceAllow'] => String[1], + Optional['DevicePolicy'] => Enum['auto','closed','strict'], + Optional['IOAccounting'] => Boolean, + Optional['IODeviceWeight'] => Array[Hash[Stdlib::Absolutepath, Integer[1,10000], 1, 1]], + Optional['IOReadBandwidthMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOReadIOPSMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOWeight'] => Integer[1,10000], + Optional['IOWriteBandwidthMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOWriteIOPSMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['MemoryAccounting'] => Boolean, + Optional['MemoryHigh'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryLimit'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], # dep'd in systemd + Optional['MemoryLow'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryMax'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryMin'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemorySwapMax'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['Slice'] => String[1], + Optional['StartupCPUShares'] => Integer[2,262144], + Optional['StartupIOWeight'] => Integer[1,10000], + Optional['TasksAccounting'] => Boolean, + Optional['TasksMax'] => Variant[Integer[1],Pattern['^(infinity|([1-9][0-9]?$|^100)%)$']], + }] +``` + ### `Systemd::Unit::Socket` Possible keys for the [Socket] section of a unit file diff --git a/manifests/manage_dropin.pp b/manifests/manage_dropin.pp index aada1e86..41761a7d 100644 --- a/manifests/manage_dropin.pp +++ b/manifests/manage_dropin.pp @@ -50,6 +50,15 @@ # } # } # +# @example set memory limits on the user slices +# systemd::manage_dropin { 'userlimits.conf': +# unit => 'user-.slice', +# slice_entry => { +# MemoryMax => '10G', +# MemoryAccounting => true, +# } +# } +# # @param unit The unit to create a dropfile for # @param filename The target unit file to create. The filename of the drop in. The full path is determined using the path, unit and this filename. # @param ensure The state of this dropin file @@ -62,6 +71,7 @@ # @param notify_service Notify a service for the unit, if it exists # @param daemon_reload Call systemd::daemon_reload # @param unit_entry key value pairs for [Unit] section of the unit file +# @param slice_entry key value pairs for [Slice] section of the unit file # @param service_entry key value pairs for [Service] section of the unit file # @param install_entry key value pairs for [Install] section of the unit file # @param timer_entry key value pairs for [Timer] section of the unit file @@ -82,6 +92,7 @@ Boolean $daemon_reload = true, Optional[Systemd::Unit::Install] $install_entry = undef, Optional[Systemd::Unit::Unit] $unit_entry = undef, + Optional[Systemd::Unit::Slice] $slice_entry = undef, Optional[Systemd::Unit::Service] $service_entry = undef, Optional[Systemd::Unit::Timer] $timer_entry = undef, Optional[Systemd::Unit::Path] $path_entry = undef, @@ -99,6 +110,10 @@ fail("Systemd::Manage_dropin[${name}]: for unit ${unit} socket_entry is only valid for socket units") } + if $slice_entry and $unit !~ Pattern['^[^/]+\.slice'] { + fail("Systemd::Manage_dropin[${name}]: for unit ${unit} slice_entry is only valid for slice units") + } + systemd::dropin_file { $name: ensure => $ensure, filename => $filename, @@ -113,6 +128,7 @@ daemon_reload => $daemon_reload, content => epp('systemd/unit_file.epp', { 'unit_entry' => $unit_entry, + 'slice_entry' => $slice_entry, 'service_entry' => $service_entry, 'install_entry' => $install_entry, 'timer_entry' => $timer_entry, diff --git a/manifests/manage_unit.pp b/manifests/manage_unit.pp index 7aefe5f9..63419e82 100644 --- a/manifests/manage_unit.pp +++ b/manifests/manage_unit.pp @@ -88,6 +88,7 @@ # call `systemd::daemon-reload` to ensure that the modified unit file is loaded # # @param unit_entry key value pairs for [Unit] section of the unit file. +# @param slice_entry key value pairs for [Slice] section of the unit file # @param service_entry key value pairs for [Service] section of the unit file. # @param install_entry key value pairs for [Install] section of the unit file. # @param timer_entry key value pairs for [Timer] section of the unit file @@ -109,6 +110,7 @@ Boolean $daemon_reload = true, Optional[Systemd::Unit::Install] $install_entry = undef, Optional[Systemd::Unit::Unit] $unit_entry = undef, + Optional[Systemd::Unit::Slice] $slice_entry = undef, Optional[Systemd::Unit::Service] $service_entry = undef, Optional[Systemd::Unit::Timer] $timer_entry = undef, Optional[Systemd::Unit::Path] $path_entry = undef, @@ -128,6 +130,10 @@ fail("Systemd::Manage_unit[${name}]: socket_entry is only valid for socket units") } + if $slice_entry and $name !~ Pattern['^[^/]+\.slice'] { + fail("Systemd::Manage_unit[${name}]: slice_entry is only valid for slice units") + } + if $ensure != 'absent' and $name =~ Pattern['^[^/]+\.service'] and !$service_entry { fail("Systemd::Manage_unit[${name}]: service_entry is required for service units") } @@ -146,6 +152,7 @@ daemon_reload => $daemon_reload, content => epp('systemd/unit_file.epp', { 'unit_entry' => $unit_entry, + 'slice_entry' => $slice_entry, 'service_entry' => $service_entry, 'install_entry' => $install_entry, 'timer_entry' => $timer_entry, diff --git a/spec/defines/manage_dropin_spec.rb b/spec/defines/manage_dropin_spec.rb index 4c0f6ef9..14110aba 100644 --- a/spec/defines/manage_dropin_spec.rb +++ b/spec/defines/manage_dropin_spec.rb @@ -37,7 +37,8 @@ is_expected.to contain_systemd__dropin_file('foobar.conf'). with_content(%r{^LimitCORE=infinity$}). with_content(%r{^DefaultDependencies=true$}). - with_content(%r{^SyslogIdentifier=simple$}) + with_content(%r{^SyslogIdentifier=simple$}). + without_content(%r{^\[Slice\]$}) } end @@ -92,6 +93,18 @@ it { is_expected.to compile.and_raise_error(%r{timer_entry is only valid for timer units}) } end + + context 'with a slice entry' do + let(:params) do + super().merge( + slice_entry: { + 'MemoryMax' => '100G', + } + ) + end + + it { is_expected.to compile.and_raise_error(%r{slice_entry is only valid for slice units}) } + end end context 'on a timer' do @@ -113,6 +126,27 @@ } end + context 'on a slice' do + let(:params) do + { + unit: 'user-.slice', + slice_entry: { + 'MemoryMax' => '10G', + 'MemoryAccounting' => true, + } + } + end + + it { is_expected.to compile.with_all_deps } + + it { + is_expected.to contain_systemd__dropin_file('foobar.conf'). + with_unit('user-.slice'). + with_content(%r{^MemoryMax=10G$}). + with_content(%r{^MemoryAccounting=true$}) + } + end + context 'on a path unit' do let(:params) do { diff --git a/spec/defines/manage_unit_spec.rb b/spec/defines/manage_unit_spec.rb index 8629a595..c72020d9 100644 --- a/spec/defines/manage_unit_spec.rb +++ b/spec/defines/manage_unit_spec.rb @@ -42,7 +42,8 @@ with_content(%r{^\[Install\]$}). with_content(%r{^Description=My great service$}). with_content(%r{^Description=has two lines of description$}). - with_content(%r{^Type=oneshot$}) + with_content(%r{^Type=oneshot$}). + without_content(%r{^\[Slice\]$}) } context 'with no service_entry' do @@ -71,6 +72,14 @@ it { is_expected.to compile.and_raise_error(%r{timer_entry is only valid for timer units}) } end + context 'with a slice entry' do + let(:params) do + super().merge(slice_entry: { 'IOWeight' => 100 }) + end + + it { is_expected.to compile.and_raise_error(%r{slice_entry is only valid for slice units}) } + end + context 'with a path entry' do let(:params) do super().merge(path_entry: { 'PathExists' => '/etc/passwd' }) @@ -161,6 +170,30 @@ } end + context 'on a slice unit' do + let(:title) { 'myslice.slice' } + let(:params) do + { + unit_entry: { + Description: 'A crazy slice', + }, + slice_entry: { + 'MemoryMax' => '10G', + 'IOAccounting' => true, + } + } + end + + it { is_expected.to compile.with_all_deps } + + it { + is_expected.to contain_systemd__unit_file('myslice.slice'). + with_content(%r{^\[Slice\]$}). + with_content(%r{^MemoryMax=10G$}). + with_content(%r{^IOAccounting=true$}) + } + end + context 'on a path unit' do let(:title) { 'etc-passwd.path' } diff --git a/spec/type_aliases/systemd_unit_slice_spec.rb b/spec/type_aliases/systemd_unit_slice_spec.rb new file mode 100644 index 00000000..9fbcdd93 --- /dev/null +++ b/spec/type_aliases/systemd_unit_slice_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Systemd::Unit::Slice' do + it { is_expected.to allow_value({ 'MemoryAccounting' => true }) } + + it { + is_expected.to allow_value( + { + 'MemoryLow' => '100', + 'MemoryMin' => '10%', + 'MemoryHigh' => '8G', + 'MemoryMax' => 'infinity', + 'MemorySwapMax' => '1T', + } + ) + } + + it { is_expected.not_to allow_value({ 'MemoryHigh' => '1P' }) } +end diff --git a/templates/unit_file.epp b/templates/unit_file.epp index 565ef567..7c606a7c 100644 --- a/templates/unit_file.epp +++ b/templates/unit_file.epp @@ -1,5 +1,6 @@ <%- | Optional[Hash] $unit_entry, + Optional[Hash] $slice_entry, Optional[Hash] $service_entry, Optional[Hash] $install_entry, Optional[Hash] $timer_entry, @@ -13,6 +14,7 @@ $_unit_sections = [ 'Unit', + 'Slice', 'Service', 'Timer', 'Path', diff --git a/types/unit/slice.pp b/types/unit/slice.pp new file mode 100644 index 00000000..ed9160bd --- /dev/null +++ b/types/unit/slice.pp @@ -0,0 +1,33 @@ +# @summary Possible keys for the [Slice] section of a unit file +# @see https://www.freedesktop.org/software/systemd/man/systemd.slice.html +# @see https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html +# +type Systemd::Unit::Slice = Struct[ + { + Optional['CPUAccounting'] => Boolean, + Optional['CPUQuota'] => Pattern['^([1-9][0-9]*)%$'], + Optional['CPUShares'] => Integer[2,262144], + Optional['Delegate'] => Boolean, + Optional['DeviceAllow'] => String[1], + Optional['DevicePolicy'] => Enum['auto','closed','strict'], + Optional['IOAccounting'] => Boolean, + Optional['IODeviceWeight'] => Array[Hash[Stdlib::Absolutepath, Integer[1,10000], 1, 1]], + Optional['IOReadBandwidthMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOReadIOPSMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOWeight'] => Integer[1,10000], + Optional['IOWriteBandwidthMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['IOWriteIOPSMax'] => Array[Hash[Stdlib::Absolutepath, Pattern['^(\d+(K|M|G|T)?)$'], 1, 1]], + Optional['MemoryAccounting'] => Boolean, + Optional['MemoryHigh'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryLimit'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], # dep'd in systemd + Optional['MemoryLow'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryMax'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemoryMin'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['MemorySwapMax'] => Pattern['\A(infinity|\d+(K|M|G|T|%)?(:\d+(K|M|G|T|%)?)?)\z'], + Optional['Slice'] => String[1], + Optional['StartupCPUShares'] => Integer[2,262144], + Optional['StartupIOWeight'] => Integer[1,10000], + Optional['TasksAccounting'] => Boolean, + Optional['TasksMax'] => Variant[Integer[1],Pattern['^(infinity|([1-9][0-9]?$|^100)%)$']], + } +]