Skip to content

Commit

Permalink
feat: automatically pick up extensions when upgrading Talos
Browse files Browse the repository at this point in the history
1 rule is enabled currently: when the node was ever running 1.5.x it
will always have `bnx` and `intel` extensions installed.

Fixes: #178

Signed-off-by: Artem Chernyshev <[email protected]>
  • Loading branch information
Unix4ever committed May 6, 2024
1 parent f40c552 commit fa3c9ff
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ package omni
import (
"context"
"errors"
"slices"

"github.com/blang/semver"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/controller/generic/qtransform"
"github.com/cosi-project/runtime/pkg/resource"
Expand Down Expand Up @@ -89,14 +91,10 @@ func (helper *schematicConfigurationHelper) reconcile(
return err
}

var extensionsList []string

if extensions != nil {
if err = updateFinalizers(ctx, r, extensions); err != nil {
return err
}

extensionsList = extensions.TypedSpec().Value.Extensions
}

clusterName, ok := clusterMachine.Metadata().Labels().Get(omni.LabelCluster)
Expand Down Expand Up @@ -131,9 +129,14 @@ func (helper *schematicConfigurationHelper) reconcile(
return err
}

machineExtensions, err := newMachineExtensions(cluster, ms, extensions)
if err != nil {
return err
}

schematicConfiguration.TypedSpec().Value.TalosVersion = cluster.TypedSpec().Value.TalosVersion

if !shouldGenerateSchematicID(cluster, extensions, ms, overlay) {
if !shouldGenerateSchematicID(cluster, machineExtensions, ms, overlay) {
// if extensions config is not set, fall back to the initial schematic id and exit
id := initialSchematic

Expand All @@ -150,7 +153,7 @@ func (helper *schematicConfigurationHelper) reconcile(
config := schematic.Schematic{
Customization: schematic.Customization{
SystemExtensions: schematic.SystemExtensions{
OfficialExtensions: extensionsList,
OfficialExtensions: machineExtensions.extensionsList,
},
},
Overlay: overlay,
Expand Down Expand Up @@ -178,19 +181,15 @@ func (helper *schematicConfigurationHelper) reconcile(
return nil
}

func shouldGenerateSchematicID(cluster *omni.Cluster, extensions *omni.MachineExtensions, machineStatus *omni.MachineStatus, overlay schematic.Overlay) bool {
func shouldGenerateSchematicID(cluster *omni.Cluster, extensionsList machineExtensions, machineStatus *omni.MachineStatus, overlay schematic.Overlay) bool {
// migrating SBC running Talos < 1.7.0, overlay was detected, but is not applied yet, should generate a schematic
if overlay.Name != "" &&
!quirks.New(machineStatus.TypedSpec().Value.InitialTalosVersion).SupportsOverlay() &&
quirks.New(cluster.TypedSpec().Value.TalosVersion).SupportsOverlay() {
return true
}

if extensions == nil || extensions.Metadata().Phase() == resource.PhaseTearingDown {
return false
}

return true
return extensionsList.shouldGenerateSchematic()
}

func getOverlay(ms *omni.MachineStatus) schematic.Overlay {
Expand All @@ -217,3 +216,82 @@ func updateFinalizers(ctx context.Context, r controller.ReaderWriter, extensions

return r.AddFinalizer(ctx, extensions.Metadata(), SchematicConfigurationControllerName)
}

type machineExtensions struct {
machineStatus *omni.MachineStatus
machineExtensions *omni.MachineExtensions
cluster *omni.Cluster
extensionsList []string
}

func newMachineExtensions(cluster *omni.Cluster, machineStatus *omni.MachineStatus, extensions *omni.MachineExtensions) (machineExtensions, error) {
me := machineExtensions{
machineStatus: machineStatus,
cluster: cluster,
}

if extensions != nil && extensions.Metadata().Phase() == resource.PhaseRunning {
me.machineExtensions = extensions
}

detected, err := getDetectedExtensions(cluster, machineStatus)
if err != nil {
return me, err
}

me.extensionsList = append(me.extensionsList, detected...)

if me.machineExtensions != nil {
for _, e := range me.machineExtensions.TypedSpec().Value.Extensions {
if slices.Contains(me.extensionsList, e) {
continue
}

me.extensionsList = append(me.extensionsList, e)
}
}

slices.Sort(me.extensionsList)

return me, nil
}

func (m machineExtensions) shouldGenerateSchematic() bool {
// generate schematic for the machine extensions when either machine extensions exists
// and contains the explicit empty list for the schematics, or when schematic list is not empty
return m.machineExtensions != nil || len(m.extensionsList) != 0
}

func getDetectedExtensions(cluster *omni.Cluster, machineStatus *omni.MachineStatus) ([]string, error) {
if machineStatus.TypedSpec().Value.InitialTalosVersion == "" {
return nil, errors.New("machine initial version is not set")
}

initialVersion, err := semver.ParseTolerant(machineStatus.TypedSpec().Value.InitialTalosVersion)
if err != nil {
return nil, err
}

currentVersion, err := semver.ParseTolerant(cluster.TypedSpec().Value.TalosVersion)
if err != nil {
return nil, err
}

currentVersion.Pre = nil

return detectExtensions(initialVersion, currentVersion)
}

func detectExtensions(initialVersion, currentVersion semver.Version) ([]string, error) {
// on 1.5.x these extensions were part of the kernel
// automatically install them when upgrading to 1.6.x+
if initialVersion.Major == 1 && initialVersion.Minor == 5 && currentVersion.GTE(semver.MustParse("1.6.0")) {
// install firmware
return []string{
"siderolabs/bnx2-bnx2x",
"siderolabs/intel-ice-firmware",
}, nil
}

return nil, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (suite *SchematicConfigurationSuite) TestReconcile() {
machineStatus.TypedSpec().Value.Schematic = &specs.MachineStatusSpec_Schematic{
InitialSchematic: initialSchematic,
}
machineStatus.TypedSpec().Value.InitialTalosVersion = "1.7.0"

clusterMachine := omni.NewClusterMachine(resources.DefaultNamespace, machineName)
clusterMachine.Metadata().Labels().Set(omni.LabelCluster, clusterName)
Expand Down Expand Up @@ -166,6 +167,65 @@ func (suite *SchematicConfigurationSuite) TestReconcile() {
assertion.Equal(initialSchematic, schematicConfiguration.TypedSpec().Value.SchematicId)
},
)

_, err = safe.StateUpdateWithConflicts(ctx, suite.state, machineStatus.Metadata(), func(res *omni.MachineStatus) error {
res.TypedSpec().Value.InitialTalosVersion = "1.5.0"

return nil
})

suite.Require().NoError(err)

rtestutils.AssertResources(ctx, suite.T(), suite.state, []string{machineName},
func(schematicConfiguration *omni.SchematicConfiguration, assertion *assert.Assertions) {
assertion.Equal("35a502528a50b5c9d264a152545c4b02c2b82a2a5c8fd7398baa9fe78abfb1a2", schematicConfiguration.TypedSpec().Value.SchematicId)
},
)

// set empty extensions list for the cluster, should keep the old schematic ID
extensionsConfiguration.TypedSpec().Value.Extensions = []string{}
extensionsConfiguration.Metadata().Labels().Set(omni.LabelCluster, clusterName)

suite.Require().NoError(suite.state.Create(ctx, extensionsConfiguration))

rtestutils.AssertResources(ctx, suite.T(), suite.state, []string{machineName},
func(schematicConfiguration *omni.SchematicConfiguration, assertion *assert.Assertions) {
assertion.Equal("35a502528a50b5c9d264a152545c4b02c2b82a2a5c8fd7398baa9fe78abfb1a2", schematicConfiguration.TypedSpec().Value.SchematicId)
},
)

// update extensions, should be still no-op as it's duplicate to what's selected by Omni
_, err = safe.StateUpdateWithConflicts(ctx, suite.state, extensionsConfiguration.Metadata(), func(res *omni.ExtensionsConfiguration) error {
res.TypedSpec().Value.Extensions = []string{"siderolabs/bnx2-bnx2x"}

return nil
})

suite.Require().NoError(err)

rtestutils.AssertResources(ctx, suite.T(), suite.state, []string{machineName},
func(schematicConfiguration *omni.SchematicConfiguration, assertion *assert.Assertions) {
assertion.Equal("35a502528a50b5c9d264a152545c4b02c2b82a2a5c8fd7398baa9fe78abfb1a2", schematicConfiguration.TypedSpec().Value.SchematicId)
},
)

// add an extra extension, schematic ID should change
_, err = safe.StateUpdateWithConflicts(ctx, suite.state, extensionsConfiguration.Metadata(), func(res *omni.ExtensionsConfiguration) error {
res.TypedSpec().Value.Extensions = []string{
"siderolabs/bnx2-bnx2x",
"siderolabs/x11",
}

return nil
})

suite.Require().NoError(err)

rtestutils.AssertResources(ctx, suite.T(), suite.state, []string{machineName},
func(schematicConfiguration *omni.SchematicConfiguration, assertion *assert.Assertions) {
assertion.Equal("5fd4ef8a66795a9aba2520a2be1bb4fb64ef7405a775e40965cf6d7aa417665f", schematicConfiguration.TypedSpec().Value.SchematicId)
},
)
}

func TestSchematicConfigurationSuite(t *testing.T) {
Expand Down

0 comments on commit fa3c9ff

Please sign in to comment.