Skip to content

Commit

Permalink
use node selector in UpdateNodes task (#103)
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Shmulevich <[email protected]>
  • Loading branch information
dmitsh authored Aug 22, 2024
1 parent bbf5a6e commit a3ac37c
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 39 deletions.
38 changes: 23 additions & 15 deletions pkg/engine/update_nodes_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type UpdateNodesTask struct {
type nodeStateParams struct {
StateParams `yaml:",inline"`

Selector *utils.NameSelector `yaml:"selector"`
Selectors []map[string]string `yaml:"selectors"`
}

func newUpdateNodesTask(client *kubernetes.Clientset, cfg *config.Task) (*UpdateNodesTask, error) {
Expand Down Expand Up @@ -74,12 +74,10 @@ func (p *nodeStateParams) validate(taskType, taskID string, params map[string]in
return fmt.Errorf("failed to parse parameters in %s task %s: %v", taskType, taskID, err)
}

if p.Selector == nil {
return fmt.Errorf("missing node selector in %s task %s", taskType, taskID)
}
if err = p.Selector.Finalize(); err != nil {
return fmt.Errorf("failed to parse parameters in %s task %s: %v", taskType, taskID, err)
if len(p.Selectors) == 0 {
return fmt.Errorf("missing node selectors in %s task %s", taskType, taskID)
}

if len(p.State) == 0 {
return fmt.Errorf("missing state parameters in %s task %s", taskType, taskID)
}
Expand All @@ -100,21 +98,31 @@ func (task *UpdateNodesTask) Exec(ctx context.Context) error {
return fmt.Errorf("%s: failed to generate patch: %v", task.ID(), err)
}

matcher := task.Selector.Matcher()
for _, node := range nodeList.Items {
if matcher.IsMatch(node.Name) {
if patch.Root != nil {
if _, err := nodeClient.Patch(ctx, node.Name, types.MergePatchType, patch.Root, metav1.PatchOptions{}); err != nil {
return err
for _, selector := range task.Selectors {
if isMapSubset(node.Labels, selector) {
if patch.Root != nil {
if _, err := nodeClient.Patch(ctx, node.Name, types.MergePatchType, patch.Root, metav1.PatchOptions{}); err != nil {
return err
}
}
}
if patch.Status != nil {
if _, err := nodeClient.PatchStatus(ctx, node.Name, patch.Status); err != nil {
return err
if patch.Status != nil {
if _, err := nodeClient.PatchStatus(ctx, node.Name, patch.Status); err != nil {
return err
}
}
}
}
}

return nil
}

func isMapSubset(mapSet, mapSubset map[string]string) bool {
for key, value := range mapSubset {
if v, ok := mapSet[key]; !ok || v != value {
return false
}
}
return true
}
97 changes: 73 additions & 24 deletions pkg/engine/update_nodes_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/stretchr/testify/require"

"github.com/NVIDIA/knavigator/pkg/config"
"github.com/NVIDIA/knavigator/pkg/utils"
)

func TestUpdateNodesTask(t *testing.T) {
Expand All @@ -33,7 +32,6 @@ func TestUpdateNodesTask(t *testing.T) {
simClients bool
err string
task *UpdateNodesTask
patch *utils.PatchData
}{
{
name: "Case 1: no k8s client",
Expand All @@ -43,60 +41,111 @@ func TestUpdateNodesTask(t *testing.T) {
{
name: "Case 2: no parameters map",
simClients: true,
err: "missing node selector in UpdateNodes task update",
err: "missing node selectors in UpdateNodes task update",
},
{
name: "Case 3: invalid params",
params: map[string]interface{}{
"selector": false,
"selectors": false,
},
simClients: true,
err: "failed to parse parameters in UpdateNodes task update: yaml: unmarshal errors:\n line 1: cannot unmarshal !!bool `false` into utils.NameSelector",
err: "failed to parse parameters in UpdateNodes task update: yaml: unmarshal errors:\n line 1: cannot unmarshal !!bool `false` into []map[string]string",
},
{
name: "Case 4: no range pattern",
name: "Case 4: missing state parameters",
params: map[string]interface{}{
"selector": map[string]interface{}{
"list": map[string]interface{}{
"patterns": []interface{}{"node1", "node2"},
},
"range": map[string]interface{}{},
},
"selectors": []map[string]string{{"key1": "val1"}},
},
simClients: true,
err: "failed to parse parameters in UpdateNodes task update: missing pattern in name range",
err: "missing state parameters in UpdateNodes task update",
},
{
name: "Case 5: missing state parameters",
name: "Case 5: valid input",
params: map[string]interface{}{
"selector": map[string]interface{}{
"list": map[string]interface{}{
"patterns": []interface{}{"node1", "node2"},
},
"range": map[string]interface{}{
"pattern": "node{{._INDEX_}}",
"ranges": []string{"2-4"},
},
"selectors": []map[string]string{{"key1": "val1"}, {"key2": "val2", "key3": "val3"}},
"state": map[string]interface{}{
"spec": map[string]interface{}{"unschedulable": true},
},
},
simClients: true,
err: "missing state parameters in UpdateNodes task update",
task: &UpdateNodesTask{
BaseTask: BaseTask{
taskType: TaskUpdateNodes,
taskID: taskID,
},
nodeStateParams: nodeStateParams{
StateParams: StateParams{
State: map[string]interface{}{
"spec": map[string]interface{}{"unschedulable": true},
},
},
Selectors: []map[string]string{{"key1": "val1"}, {"key2": "val2", "key3": "val3"}},
},
client: testK8sClient,
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
eng, err := New(nil, nil, tc.simClients)
require.NoError(t, err)
_, err = eng.GetTask(&config.Task{
task, err := eng.GetTask(&config.Task{
ID: taskID,
Type: TaskUpdateNodes,
Params: tc.params,
})
if len(tc.err) != 0 {
require.EqualError(t, err, tc.err)
require.Nil(t, tc.task)
} else {
require.NoError(t, err)
require.Equal(t, tc.task, task)
}
})
}
}

func TestIsMapSubset(t *testing.T) {
testCases := []struct {
name string
set map[string]string
subset map[string]string
res bool
}{
{
name: "Case 1: empty subset",
set: map[string]string{"key1": "val1"},
res: true,
},
{
name: "Case 2: empty set",
subset: map[string]string{"key1": "val1"},
res: false,
},
{
name: "Case 3: equal sets",
set: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
subset: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
res: true,
},
{
name: "Case 4: valid subset",
set: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"},
subset: map[string]string{"key1": "val1"},
res: true,
},
{
name: "Case 5: invalid subset",
set: map[string]string{"key1": "val1", "key2": "val2"},
subset: map[string]string{"key3": "val3"},
res: false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.res, isMapSubset(tc.set, tc.subset))
})
}
}

0 comments on commit a3ac37c

Please sign in to comment.