mirror of
https://github.com/siderolabs/omni.git
synced 2026-05-09 00:26:12 +02:00
To avoid unwanted upgrades, compare the kernel args more conservatively, with partially ignoring order: - protected args need to be equal, ignoring order completely - non-protected args need to be equal, respecting the order - protected and non-protected args can appear in any order relative to each other, they can also be interleaved/scattered. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
165 lines
5.6 KiB
Go
165 lines
5.6 KiB
Go
// Copyright (c) 2026 Sidero Labs, Inc.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the LICENSE file.
|
|
|
|
package kernelargs_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/siderolabs/omni/client/api/omni/specs"
|
|
"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
|
|
"github.com/siderolabs/omni/internal/backend/kernelargs"
|
|
)
|
|
|
|
// Protected args as seen on a real machine.
|
|
const (
|
|
siderolink = "siderolink.api=https://omni.example.com?jointoken=example"
|
|
eventsSink = "talos.events.sink=[fdae:41e4:649b:9303::1]:8091"
|
|
logging = "talos.logging.kernel=tcp://[fdae:41e4:649b:9303::1]:8092"
|
|
)
|
|
|
|
func machineStatusWithArgs(currentArgs []string) *omni.MachineStatus {
|
|
ms := omni.NewMachineStatus("test-machine")
|
|
ms.Metadata().Annotations().Set(omni.KernelArgsInitialized, "")
|
|
ms.TypedSpec().Value.Schematic = &specs.MachineStatusSpec_Schematic{
|
|
Id: "some-id",
|
|
FullId: "some-full-id",
|
|
KernelArgs: currentArgs,
|
|
}
|
|
|
|
return ms
|
|
}
|
|
|
|
func kernelArgsResource(userArgs []string) *omni.KernelArgs {
|
|
if userArgs == nil {
|
|
return nil
|
|
}
|
|
|
|
ka := omni.NewKernelArgs("test-machine")
|
|
ka.TypedSpec().Value.Args = userArgs
|
|
|
|
return ka
|
|
}
|
|
|
|
// TestCalculateOrderStability verifies that Calculate preserves the current kernel args
|
|
// when they are logically equal to the calculated ones, preventing spurious schematic ID
|
|
// changes (and therefore unintended machine upgrades/reboots) caused by arg reordering.
|
|
func TestCalculateOrderStability(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
currentArgs []string
|
|
userArgs []string
|
|
expected []string
|
|
}{
|
|
{
|
|
// Real-world interleaving: user and protected args fully mixed.
|
|
// Logically equal → preserve the machine's current order.
|
|
name: "equal: fully interleaved",
|
|
currentArgs: []string{"nomodeset", siderolink, "net.ifnames=0", eventsSink, logging},
|
|
userArgs: []string{"nomodeset", "net.ifnames=0"},
|
|
expected: []string{"nomodeset", siderolink, "net.ifnames=0", eventsSink, logging},
|
|
},
|
|
{
|
|
// Another interleaving: protected args scattered around user args.
|
|
name: "equal: protected args scattered",
|
|
currentArgs: []string{siderolink, "nomodeset", logging, "net.ifnames=0", eventsSink},
|
|
userArgs: []string{"nomodeset", "net.ifnames=0"},
|
|
expected: []string{siderolink, "nomodeset", logging, "net.ifnames=0", eventsSink},
|
|
},
|
|
{
|
|
// Current is already in canonical order (protected first) → preserve current.
|
|
name: "equal: already in canonical order",
|
|
currentArgs: []string{siderolink, eventsSink, logging, "nomodeset", "net.ifnames=0"},
|
|
userArgs: []string{"nomodeset", "net.ifnames=0"},
|
|
expected: []string{siderolink, eventsSink, logging, "nomodeset", "net.ifnames=0"},
|
|
},
|
|
{
|
|
// No user args on the machine and no user args resource → preserve current.
|
|
name: "equal: only protected args, no user args",
|
|
currentArgs: []string{siderolink, eventsSink, logging},
|
|
userArgs: nil,
|
|
expected: []string{siderolink, eventsSink, logging},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result, initialized, err := kernelargs.Calculate(
|
|
machineStatusWithArgs(tc.currentArgs),
|
|
kernelArgsResource(tc.userArgs),
|
|
)
|
|
|
|
assert.NoError(t, err)
|
|
assert.True(t, initialized)
|
|
assert.Equal(t, tc.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCalculateCanonicalOrderWhenNotEqual verifies that when the current kernel args
|
|
// are not logically equal to the calculated ones, Calculate returns the canonical
|
|
// ordering (calculatedArgs: protected first, then user args) instead of preserving
|
|
// the machine's current order.
|
|
func TestCalculateCanonicalOrderWhenNotEqual(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
currentArgs []string
|
|
userArgs []string
|
|
expected []string
|
|
}{
|
|
{
|
|
// User changed their kernel args: "nomodeset" replaced by "quiet".
|
|
// Not equal → return canonical order (protected first, then user args).
|
|
name: "not equal: different user args",
|
|
currentArgs: []string{siderolink, eventsSink, logging, "nomodeset"},
|
|
userArgs: []string{"quiet"},
|
|
expected: []string{siderolink, eventsSink, logging, "quiet"},
|
|
},
|
|
{
|
|
// User reordered their kernel args resource.
|
|
// Not equal → canonical order follows the desired user arg order.
|
|
name: "not equal: user args reordered",
|
|
currentArgs: []string{siderolink, eventsSink, logging, "b", "a"},
|
|
userArgs: []string{"a", "b"},
|
|
expected: []string{siderolink, eventsSink, logging, "a", "b"},
|
|
},
|
|
{
|
|
// User removed a kernel arg from their resource.
|
|
// Not equal → canonical order reflects only the desired user args.
|
|
name: "not equal: user arg removed",
|
|
currentArgs: []string{siderolink, eventsSink, logging, "nomodeset", "net.ifnames=0"},
|
|
userArgs: []string{"nomodeset"},
|
|
expected: []string{siderolink, eventsSink, logging, "nomodeset"},
|
|
},
|
|
{
|
|
// User added a new kernel arg to their resource.
|
|
// Not equal → canonical order includes all desired user args.
|
|
name: "not equal: user arg added",
|
|
currentArgs: []string{siderolink, eventsSink, logging, "nomodeset"},
|
|
userArgs: []string{"nomodeset", "net.ifnames=0"},
|
|
expected: []string{siderolink, eventsSink, logging, "nomodeset", "net.ifnames=0"},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result, initialized, err := kernelargs.Calculate(
|
|
machineStatusWithArgs(tc.currentArgs),
|
|
kernelArgsResource(tc.userArgs),
|
|
)
|
|
|
|
assert.NoError(t, err)
|
|
assert.True(t, initialized)
|
|
assert.Equal(t, tc.expected, result)
|
|
})
|
|
}
|
|
}
|