-
Notifications
You must be signed in to change notification settings - Fork 557
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds support for custom node taints. Refer to `nodeTaints` in the `configuration` for more information. Closes #7581 Signed-off-by: Dmitriy Matrenichev <[email protected]>
- Loading branch information
Showing
18 changed files
with
409 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
//go:build integration_api | ||
|
||
package api | ||
|
||
import ( | ||
"context" | ||
"slices" | ||
"strings" | ||
"time" | ||
|
||
"github.com/siderolabs/gen/maps" | ||
"github.com/siderolabs/gen/xslices" | ||
"github.com/siderolabs/gen/xtesting/must" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/watch" | ||
|
||
"github.com/siderolabs/talos/internal/integration/base" | ||
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine" | ||
"github.com/siderolabs/talos/pkg/machinery/client" | ||
"github.com/siderolabs/talos/pkg/machinery/config/container" | ||
"github.com/siderolabs/talos/pkg/machinery/config/machine" | ||
) | ||
|
||
// NodeTaintsSuite verifies updating node taints via machine config. | ||
type NodeTaintsSuite struct { | ||
base.K8sSuite | ||
|
||
ctx context.Context //nolint:containedctx | ||
ctxCancel context.CancelFunc | ||
} | ||
|
||
// SuiteName ... | ||
func (suite *NodeTaintsSuite) SuiteName() string { | ||
return "api.NodeTaintsSuite" | ||
} | ||
|
||
// SetupTest ... | ||
func (suite *NodeTaintsSuite) SetupTest() { | ||
// make sure API calls have timeout | ||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 5*time.Minute) | ||
} | ||
|
||
// TearDownTest ... | ||
func (suite *NodeTaintsSuite) TearDownTest() { | ||
if suite.ctxCancel != nil { | ||
suite.ctxCancel() | ||
} | ||
} | ||
|
||
// TestUpdateControlPlane verifies node label updates on control plane nodes. | ||
func (suite *NodeTaintsSuite) TestUpdateControlPlane() { | ||
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeControlPlane) | ||
|
||
suite.testUpdate(node) | ||
} | ||
|
||
// TestUpdateWorker verifies node label updates on worker nodes. | ||
func (suite *NodeTaintsSuite) TestUpdateWorker() { | ||
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker) | ||
|
||
suite.testUpdate(node) | ||
} | ||
|
||
// testUpdate cycles through a set of node label updates reverting the change in the end. | ||
func (suite *NodeTaintsSuite) testUpdate(node string) { | ||
k8sNode, err := suite.GetK8sNodeByInternalIP(suite.ctx, node) | ||
suite.Require().NoError(err) | ||
|
||
suite.T().Logf("updating taints on node %q (%q)", node, k8sNode.Name) | ||
|
||
watcher, err := suite.Clientset.CoreV1().Nodes().Watch(suite.ctx, metav1.ListOptions{ | ||
FieldSelector: metadataKeyName + k8sNode.Name, | ||
Watch: true, | ||
}) | ||
suite.Require().NoError(err) | ||
|
||
defer watcher.Stop() | ||
|
||
// set two new labels | ||
suite.setNodeTaints(node, map[string]string{ | ||
"talos.dev/test1": "value1:NoSchedule", | ||
"talos.dev/test2": "value2:NoSchedule", | ||
}) | ||
|
||
suite.waitUntil(watcher, map[string]string{ | ||
"talos.dev/test1": "value1:NoSchedule", | ||
"talos.dev/test2": "value2:NoSchedule", | ||
}) | ||
|
||
// remove one label owned by Talos | ||
suite.setNodeTaints(node, map[string]string{ | ||
"talos.dev/test1": "value1:NoSchedule", | ||
}) | ||
|
||
suite.waitUntil(watcher, map[string]string{ | ||
"talos.dev/test1": "value1:NoSchedule", | ||
}) | ||
|
||
// remove all Talos Labels | ||
suite.setNodeTaints(node, nil) | ||
|
||
suite.waitUntil(watcher, nil) | ||
} | ||
|
||
func (suite *NodeTaintsSuite) waitUntil(watcher watch.Interface, expectedTaints map[string]string) { | ||
outer: | ||
for { | ||
select { | ||
case ev := <-watcher.ResultChan(): | ||
k8sNode, ok := ev.Object.(*v1.Node) | ||
suite.Require().True(ok, "watch event is not of type v1.Node") | ||
|
||
suite.T().Logf("labels %v, taints %v", k8sNode.Labels, k8sNode.Spec.Taints) | ||
|
||
taints := xslices.ToMap(k8sNode.Spec.Taints, func(t v1.Taint) (string, string) { | ||
return t.Key, strings.Join([]string{t.Value, string(t.Effect)}, ":") | ||
}) | ||
|
||
expectedTaintsKeys := maps.Keys(expectedTaints) | ||
|
||
slices.Sort(expectedTaintsKeys) | ||
|
||
for _, key := range expectedTaintsKeys { | ||
actualValue, ok := taints[key] | ||
if !ok { | ||
suite.T().Logf("taint %q is not present", key) | ||
|
||
continue outer | ||
} | ||
|
||
expectedValue := expectedTaints[key] | ||
|
||
if expectedValue != actualValue { | ||
suite.T().Logf("taint %q is not %q", key, expectedValue) | ||
|
||
continue outer | ||
} | ||
|
||
delete(taints, key) | ||
} | ||
|
||
if len(taints) > 0 { | ||
keys := maps.Keys(taints) | ||
|
||
slices.Sort(keys) | ||
|
||
suite.T().Logf("taints %v are still present", keys) | ||
|
||
continue outer | ||
} | ||
|
||
return | ||
case <-suite.ctx.Done(): | ||
suite.T().Fatal("timeout") | ||
} | ||
} | ||
} | ||
|
||
func (suite *NodeTaintsSuite) setNodeTaints(nodeIP string, nodeTaints map[string]string) { | ||
nodeCtx := client.WithNode(suite.ctx, nodeIP) | ||
|
||
nodeConfig := must.Value(suite.ReadConfigFromNode(nodeCtx))(suite.T()) | ||
|
||
nodeConfigRaw := nodeConfig.RawV1Alpha1() | ||
suite.Require().NotNil(nodeConfigRaw, "node config is not of type v1alpha1.Config") | ||
|
||
nodeConfigRaw.MachineConfig.MachineNodeTaints = nodeTaints | ||
|
||
bytes := must.Value(container.NewV1Alpha1(nodeConfigRaw).Bytes())(suite.T()) | ||
|
||
must.Value(suite.Client.ApplyConfiguration(nodeCtx, &machineapi.ApplyConfigurationRequest{ | ||
Data: bytes, | ||
Mode: machineapi.ApplyConfigurationRequest_NO_REBOOT, | ||
}))(suite.T()) | ||
} | ||
|
||
func init() { | ||
allSuites = append(allSuites, new(NodeTaintsSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.