diff --git a/REFERENCE.md b/REFERENCE.md
index e65d56f..b46ef97 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -128,6 +128,7 @@ The following parameters are available in the `ipmi::network` defined type:
* [`gateway`](#-ipmi--network--gateway)
* [`type`](#-ipmi--network--type)
* [`lan_channel`](#-ipmi--network--lan_channel)
+* [`interface_type`](#-ipmi--network--interface_type)
##### `ip`
@@ -170,6 +171,17 @@ Defaults to the first detected lan channel, starting at 1 ending at 11
Default value: `undef`
+##### `interface_type`
+
+Data type: `Optional[Enum['dedicated', 'shared', 'failover']]`
+
+Controls the NIC selection mode for the BMC.
+Supported values: 'dedicated', 'shared', 'failover'.
+Only applied when the manufacturer is known (Supermicro or Dell).
+Leave undef to skip interface type configuration.
+
+Default value: `undef`
+
### `ipmi::snmp`
Manage SNMP community strings
diff --git a/manifests/network.pp b/manifests/network.pp
index 32338e9..0057389 100644
--- a/manifests/network.pp
+++ b/manifests/network.pp
@@ -11,18 +11,24 @@
# @param lan_channel
# Controls the lan channel of the IPMI network to be configured.
# Defaults to the first detected lan channel, starting at 1 ending at 11
+# @param interface_type
+# Controls the NIC selection mode for the BMC.
+# Supported values: 'dedicated', 'shared', 'failover'.
+# Only applied when the manufacturer is known (Supermicro or Dell).
+# Leave undef to skip interface type configuration.
#
define ipmi::network (
- Stdlib::IP::Address $ip = '0.0.0.0',
- Stdlib::IP::Address $netmask = '255.255.255.0',
- Stdlib::IP::Address $gateway = '0.0.0.0',
- Enum['dhcp', 'static'] $type = 'dhcp',
- Optional[Integer] $lan_channel = undef
+ Stdlib::IP::Address $ip = '0.0.0.0',
+ Stdlib::IP::Address $netmask = '255.255.255.0',
+ Stdlib::IP::Address $gateway = '0.0.0.0',
+ Enum['dhcp', 'static'] $type = 'dhcp',
+ Optional[Integer] $lan_channel = undef,
+ Optional[Enum['dedicated', 'shared', 'failover']] $interface_type = undef,
) {
require ipmi::install
$_real_lan_channel = $lan_channel ? {
- undef => $ipmi::default_channel,
+ undef => $ipmi::default_channel,
default => $lan_channel,
}
@@ -62,4 +68,52 @@
'Subnet Mask' | sed -e 's/.* : //g')\" != \"${netmask}\"",
}
}
+
+ if $interface_type != undef {
+ $_manufacturer = $facts.dig('ipmitool_mc_info', 'Manufacturer Name')
+ if $_manufacturer != undef {
+ case $_manufacturer {
+ 'Supermicro': {
+ $_raw_check = '0x30 0x70 0x0c'
+ case $interface_type {
+ 'dedicated': { $_iface_code = 0 }
+ 'shared': { $_iface_code = 1 }
+ 'failover': { $_iface_code = 2 }
+ default: { $_iface_code = undef }
+ }
+ if $_iface_code != undef {
+ # lint:ignore:140chars
+ exec { "ipmi_set_interface_type_${_real_lan_channel}":
+ command => "/usr/bin/ipmitool raw ${_raw_check} 1 ${_iface_code}",
+ onlyif => "/usr/bin/test $(/usr/bin/ipmitool raw ${_raw_check} 0) -ne ${_iface_code}",
+ }
+ # lint:endignore
+ }
+ }
+ 'Dell': {
+ $_raw_set = '0x30 0x24'
+ $_raw_check = '0x30 0x25'
+ case $interface_type {
+ 'dedicated': { $_iface_code = 2 }
+ 'shared': { $_iface_code = 0 }
+ 'failover': { $_iface_code = 1 }
+ default: { $_iface_code = undef }
+ }
+ if $_iface_code != undef {
+ # lint:ignore:140chars
+ exec { "ipmi_set_interface_type_${_real_lan_channel}":
+ command => "/usr/bin/ipmitool raw ${_raw_set} ${_iface_code}",
+ onlyif => "/usr/bin/test $(/usr/bin/ipmitool raw ${_raw_check}) -ne ${_iface_code}",
+ }
+ # lint:endignore
+ }
+ }
+ default: {
+ # lint:ignore:140chars
+ warning("${_manufacturer} does not have interface type behavior defined in this module. Can you submit it?")
+ # lint:endignore
+ }
+ }
+ }
+ }
}
diff --git a/spec/defines/ipmi_network_spec.rb b/spec/defines/ipmi_network_spec.rb
index 09e6e6e..cda5001 100644
--- a/spec/defines/ipmi_network_spec.rb
+++ b/spec/defines/ipmi_network_spec.rb
@@ -5,24 +5,24 @@
describe 'ipmi::network', type: :define do
on_supported_os.each do |os, facts|
context "on #{os}" do
+ let(:title) { 'example' }
+
+ # Base facts without manufacturer — used by most existing tests.
+ # Interface type tests override this via subject-level let(:facts).
let(:facts) do
facts.merge(
{
ipmitool_mc_info: { IPMI_Puppet_Service_Recommend: 'running' },
- ipmi: { default: { channel: 1 } }
+ ipmi: { default: { channel: 1 } },
}
)
end
- let(:title) { 'example' }
describe 'when deploying as dhcp with minimal params' do
- let(:params) do
- {
- type: 'dhcp',
- }
- end
+ let(:params) { { type: 'dhcp' } }
it { is_expected.to contain_exec('ipmi_set_dhcp_1') }
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
end
describe 'when deploying as dhcp with all params' do
@@ -37,6 +37,7 @@
end
it { is_expected.to contain_exec('ipmi_set_dhcp_1') }
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
end
describe 'when deploying as static with minimal params' do
@@ -52,6 +53,7 @@
it { is_expected.to contain_exec('ipmi_set_static_1').that_notifies('Exec[ipmi_set_ipaddr_1]') }
it { is_expected.to contain_exec('ipmi_set_static_1').that_notifies('Exec[ipmi_set_defgw_1]') }
it { is_expected.to contain_exec('ipmi_set_static_1').that_notifies('Exec[ipmi_set_netmask_1]') }
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
end
describe 'when deploying as static with all params' do
@@ -68,6 +70,110 @@
it { is_expected.to contain_exec('ipmi_set_static_2').that_notifies('Exec[ipmi_set_ipaddr_2]') }
it { is_expected.to contain_exec('ipmi_set_static_2').that_notifies('Exec[ipmi_set_defgw_2]') }
it { is_expected.to contain_exec('ipmi_set_static_2').that_notifies('Exec[ipmi_set_netmask_2]') }
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_2') }
+ end
+
+ describe 'interface_type' do
+ # Shared params for all interface_type tests — type itself is irrelevant
+ # to interface configuration, so use dhcp to keep tests focused.
+ let(:base_params) { { type: 'dhcp', lan_channel: 1 } }
+
+ describe 'when manufacturer fact is absent' do
+ let(:params) { base_params.merge(interface_type: 'dedicated') }
+
+ # ipmitool_mc_info present but no Manufacturer Name key
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
+ end
+
+ describe 'when manufacturer is unknown' do
+ let(:facts) do
+ facts.merge(
+ {
+ ipmitool_mc_info: {
+ IPMI_Puppet_Service_Recommend: 'running',
+ 'Manufacturer Name' => 'HPE',
+ },
+ ipmi: { default: { channel: 1 } },
+ }
+ )
+ end
+ let(:params) { base_params.merge(interface_type: 'dedicated') }
+
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
+ it { is_expected.to compile.with_warnings }
+ end
+
+ describe 'Supermicro' do
+ let(:facts) do
+ facts.merge(
+ {
+ ipmitool_mc_info: {
+ IPMI_Puppet_Service_Recommend: 'running',
+ 'Manufacturer Name' => 'Supermicro',
+ },
+ ipmi: { default: { channel: 1 } },
+ }
+ )
+ end
+
+ { 'dedicated' => 0, 'shared' => 1, 'failover' => 2 }.each do |iface_type, code|
+ describe "interface_type => #{iface_type}" do
+ let(:params) { base_params.merge(interface_type: iface_type) }
+
+ it { is_expected.to contain_exec('ipmi_set_interface_type_1')
+ .with_command("/usr/bin/ipmitool raw 0x30 0x70 0x0c 1 #{code}")
+ .with_onlyif("/usr/bin/test $(/usr/bin/ipmitool raw 0x30 0x70 0x0c 0) -ne #{code}") }
+ end
+ end
+ end
+
+ describe 'Dell' do
+ let(:facts) do
+ facts.merge(
+ {
+ ipmitool_mc_info: {
+ IPMI_Puppet_Service_Recommend: 'running',
+ 'Manufacturer Name' => 'Dell',
+ },
+ ipmi: { default: { channel: 1 } },
+ }
+ )
+ end
+
+ { 'dedicated' => 2, 'shared' => 0, 'failover' => 1 }.each do |iface_type, code|
+ describe "interface_type => #{iface_type}" do
+ let(:params) { base_params.merge(interface_type: iface_type) }
+
+ it { is_expected.to contain_exec('ipmi_set_interface_type_1')
+ .with_command("/usr/bin/ipmitool raw 0x30 0x24 #{code}")
+ .with_onlyif("/usr/bin/test $(/usr/bin/ipmitool raw 0x30 0x25) -ne #{code}") }
+ end
+ end
+ end
+
+ describe 'when interface_type is undef' do
+ let(:facts) do
+ facts.merge(
+ {
+ ipmitool_mc_info: {
+ IPMI_Puppet_Service_Recommend: 'running',
+ 'Manufacturer Name' => 'Supermicro',
+ },
+ ipmi: { default: { channel: 1 } },
+ }
+ )
+ end
+ let(:params) { base_params }
+
+ # Manufacturer present but interface_type not requested — no exec created
+ it { is_expected.not_to contain_exec('ipmi_set_interface_type_1') }
+ end
+
+ describe 'invalid interface_type value' do
+ let(:params) { base_params.merge(interface_type: 'bonded') }
+
+ it { is_expected.to compile.and_raise_error(%r{interface_type}) }
+ end
end
end
end