Merge pull request #1678 from anderbubble/slice-negation

Fixed negation for slice field elements during profile/node merge
This commit is contained in:
Christian Goll
2025-02-01 13:32:28 +01:00
committed by GitHub
6 changed files with 156 additions and 71 deletions

View File

@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix `make install` when `sudo` does not set `$PWD`. #1660
- Use sh to parse and exec IPMI command. #1663
- Use configured warewulf.conf path in `wwctl upgrade`. #1658
- Fixed negation for slice field elements during profile/node merge. #1677
## v4.6.0rc1, 2025-01-29

View File

@@ -267,7 +267,7 @@ nodes:
`,
},
{
name: "multiple overlays list long with negation",
name: "multiple overlays list long",
args: []string{"-l"},
wantErr: false,
stdout: `
@@ -287,13 +287,13 @@ nodes:
`,
},
{
name: "multiple overlays list long",
name: "multiple overlays list long with negation",
args: []string{"-l"},
wantErr: false,
stdout: `
NODE NAME KERNEL VERSION IMAGE OVERLAYS (S/R)
--------- -------------- ----- --------------
n01 -- -- sop1/rop1,rop2,nop1,~rop1
n01 -- -- sop1/rop2,nop1
`,
inDb: `nodeprofiles:
p1:

View File

@@ -214,7 +214,7 @@ nodes:
assert.Contains(nodemap, "node4")
assert.ElementsMatch(nodemap["node4"].RuntimeOverlay, []string{"p1o1", "p1o2", "p2o1", "p2o2", "n1o1", "n1o2"})
assert.Contains(nodemap, "node5")
assert.ElementsMatch(nodemap["node5"].RuntimeOverlay, []string{"p1o1", "p1o2", "~p1o2", "p2o1", "p2o2", "n1o1"})
assert.ElementsMatch(nodemap["node5"].RuntimeOverlay, []string{"p1o1", "p2o1", "p2o2", "n1o1"})
}
func Test_negated_list(t *testing.T) {

View File

@@ -153,6 +153,7 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
node.id = id
node.valid = true
node.updatePrimaryNetDev()
node.Profile.cleanLists()
return node, fields, nil
}

View File

@@ -194,16 +194,18 @@ nodeprofiles:
func Test_MergeNode(t *testing.T) {
var tests = map[string]struct {
nodesConf string
node string
field string
source string
value string
nodes []string
fields []string
sources []string
values []string
jsonValues []string
nodesConf string
node string
field string
source string
value string
fieldValue string
nodes []string
fields []string
sources []string
values []string
fieldValues []string
jsonValues bool
}{
"node comment": {
nodesConf: `
@@ -585,6 +587,9 @@ nodeprofiles:
},
"node profiles": {
nodesConf: `
nodeprofiles:
p1: {}
p2: {}
nodes:
n1:
profiles:
@@ -604,7 +609,8 @@ nodes:
nodeprofiles:
p1:
profiles:
- p2`,
- p2
p2: {}`,
node: "n1",
field: "Profiles",
source: "",
@@ -792,7 +798,92 @@ nodes:
nodes: []string{"n1"},
fields: []string{"Resources[fstab]"},
sources: []string{"p1,n1"},
jsonValues: []string{`[{"file":"/home","spec":"warewulf:/home","vfstype":"nfs"},{"file":"/opt","spec":"warewulf:/opt","vfstype":"nfs"}]`},
values: []string{`[{"file":"/home","spec":"warewulf:/home","vfstype":"nfs"},{"file":"/opt","spec":"warewulf:/opt","vfstype":"nfs"}]`},
jsonValues: true,
},
"node1 gets kernel data from profile": {
nodesConf: `
nodeprofiles:
default:
kernel:
version: v1.0.0
args:
- arg1
- arg2
nodes:
node1:
profiles:
- default`,
nodes: []string{"node1", "node1"},
fields: []string{"Kernel.Version", "Kernel.Args"},
values: []string{"v1.0.0", "arg1,arg2"},
},
"node can negate kernel args from profile": {
nodesConf: `
nodeprofiles:
default:
kernel:
version: v1.0.0
args:
- arg1
- arg2
nodes:
node1:
profiles:
- default
kernel:
args:
- ~arg2
- arg3`,
nodes: []string{"node1", "node1"},
fields: []string{"Kernel.Version", "Kernel.Args"},
sources: []string{"default", "default,node1"},
values: []string{"v1.0.0", "arg1,arg3"},
fieldValues: []string{"v1.0.0", "arg1,arg2,~arg2,arg3"},
},
"node1 doesn't get interference data from node2": {
nodesConf: `
nodeprofiles:
default: {}
nodes:
node1: {}
node2:
profiles:
- default
kernel:
version: v1.0.0
args:
- kernel-args`,
nodes: []string{"node1", "node1", "node2", "node2"},
fields: []string{"Kernel.Version", "Kernel.Args", "Kernel.Version", "Kernel.Args"},
values: []string{"UNDEF", "UNDEF", "v1.0.0", "kernel-args"},
fieldValues: []string{"", "", "v1.0.0", "kernel-args"},
},
"overlay negation": {
nodesConf: `
nodeprofiles:
default:
runtime overlay:
- ro1
- ro2
system overlay:
- so1
- so2
nodes:
n1:
profiles:
- default
runtime overlay:
- ~ro1
- ro3
system overlay:
- so4
- ~so2`,
nodes: []string{"n1", "n1"},
fields: []string{"RuntimeOverlay", "SystemOverlay"},
values: []string{"ro2,ro3", "so1,so4"},
fieldValues: []string{"ro1,ro2,~ro1,ro3", "so1,so2,so4,~so2"},
sources: []string{"default,n1", "default,n1"},
},
}
@@ -811,31 +902,53 @@ nodes:
value, valueErr := getNestedFieldString(node, tt.field)
assert.NoError(t, valueErr)
assert.Equal(t, tt.value, value)
assert.Equal(t, tt.value, fields.Value(tt.field))
ttFieldValue := tt.value
if tt.fieldValue != "" {
ttFieldValue = tt.fieldValue
}
if tt.jsonValues {
assert.JSONEq(t, tt.value, value)
assert.JSONEq(t, ttFieldValue, fields.Value(tt.field))
} else {
assert.Equal(t, tt.value, value)
assert.Equal(t, ttFieldValue, fields.Value(tt.field))
}
assert.Equal(t, tt.source, fields.Source(tt.field))
}
var nodes []Node
for i := range tt.nodes {
node, _, mergeErr := registry.MergeNode(tt.nodes[i])
node, fields, mergeErr := registry.MergeNode(tt.nodes[i])
assert.NoError(t, mergeErr)
nodes = append(nodes, node)
}
for i := range tt.nodes {
_, fields, _ := registry.MergeNode(tt.nodes[i])
value, valueErr := getNestedFieldString(nodes[i], tt.fields[i])
assert.NoError(t, valueErr)
value, valueErr := getNestedFieldString(node, tt.fields[i])
if valueErr != nil {
value = "UNDEF"
}
if len(tt.values) > i {
assert.Equal(t, tt.values[i], value)
assert.Equal(t, tt.values[i], fields.Value(tt.fields[i]))
if tt.jsonValues {
assert.JSONEq(t, tt.values[i], value)
} else {
assert.Equal(t, tt.values[i], value)
}
}
if len(tt.jsonValues) > i {
assert.JSONEq(t, tt.jsonValues[i], value)
assert.Equal(t, tt.jsonValues[i], fields.Value(tt.fields[i]))
if len(tt.fields) > i {
ttFieldValues := tt.values
if len(tt.fieldValues) > 0 {
ttFieldValues = tt.fieldValues
}
if tt.jsonValues {
assert.JSONEq(t, ttFieldValues[i], fields.Value(tt.fields[i]))
} else {
assert.Equal(t, ttFieldValues[i], fields.Value(tt.fields[i]))
}
}
if len(tt.sources) > i {
assert.Equal(t, tt.sources[i], fields.Source(tt.fields[i]))
}
assert.Equal(t, tt.sources[i], fields.Source(tt.fields[i]))
}
})
}
@@ -963,45 +1076,6 @@ nodes:
}
}
func Test_MergeNodeKernel(t *testing.T) {
var tests = map[string]struct {
nodesConf string
node string
kernel *KernelConf
}{
"interference": {
nodesConf: `
nodeprofiles:
default: {}
nodes:
node1: {}
test:
profiles:
- default
kernel:
version: v1.0.0
args:
- kernel-args`,
node: "node1",
kernel: nil,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.WriteFile("/etc/warewulf/nodes.conf", tt.nodesConf)
registry, regErr := New()
assert.NoError(t, regErr)
node, _, mergeErr := registry.MergeNode(tt.node)
assert.NoError(t, mergeErr)
assert.Equal(t, tt.kernel, node.Kernel)
})
}
}
func Test_MergeNodeIpmi(t *testing.T) {
var tests = map[string]struct {
nodesConf string

View File

@@ -382,6 +382,15 @@ func negList(list []string) (ret []string) {
return
}
func (p *Profile) cleanLists() {
p.Profiles = cleanList(p.Profiles)
p.SystemOverlay = cleanList(p.SystemOverlay)
p.RuntimeOverlay = cleanList(p.RuntimeOverlay)
if p.Kernel != nil {
p.Kernel.Args = cleanList(p.Kernel.Args)
}
}
// clean a list from negated tokens
func cleanList(list []string) (ret []string) {
neg := negList(list)