Skip to content

Commit 6735d65

Browse files
committed
Add unit tests to SMBIOS support feature
1 parent 567f0ef commit 6735d65

9 files changed

Lines changed: 148 additions & 59 deletions

File tree

mantle/cmd/kola/options.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ func init() {
6767
sv(&kola.Sharding, "sharding", "", "Provide e.g. 'hash:m/n' where m and n are integers, 1 <= m <= n. Only tests hashing to m will be run.")
6868
bv(&kola.Options.SSHOnTestFailure, "ssh-on-test-failure", false, "SSH into a machine when tests fail")
6969
bv(&kola.QEMUOptions.NoIgnition, "no-ignition", false, "Run without Ignition; provision SSH via systemd SMBIOS credentials (requires -p qemu and --qemu-image)")
70-
sv(&kola.QEMUOptions.SSHUser, "ssh-user", "", "SSH user when using --no-ignition (default: root)")
7170
sv(&kola.Options.Stream, "stream", "", "CoreOS stream ID (e.g. for Fedora CoreOS: stable, testing, next)")
7271
sv(&kola.Options.CosaWorkdir, "workdir", "", "coreos-assembler working directory")
7372
sv(&kola.Options.CosaBuildId, "build", "", "coreos-assembler build ID (or e.g. -1, -2, for previous builds)")

mantle/kola/harness.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,10 +1821,9 @@ func runTest(h *harness.H, t *register.Test, pltfrm string, flight platform.Flig
18211821
WarningsAction: conf.FailWarnings,
18221822
EarlyRelease: h.Release,
18231823
TestExecTimeout: h.TimeoutContext(),
1824-
NoIgnition: QEMUOptions.NoIgnition,
1825-
SSHUser: QEMUOptions.SSHUser,
1824+
NoIgnition: QEMUOptions.NoIgnition,
18261825
}
1827-
if QEMUOptions.NoIgnition && rconf.SSHUser == "" {
1826+
if QEMUOptions.NoIgnition {
18281827
rconf.SSHUser = "root"
18291828
}
18301829
if t.HasFlag(register.AllowConfigWarnings) {

mantle/kola/tests/coretest/core.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ func init() {
111111
},
112112
// FIXME run on RHCOS once it has https://github.com/coreos/ignition-dracut/pull/93
113113
Distros: []string{"fcos"},
114-
Tags: []string{kola.BootcBaseTag},
115114
})
116115
register.RegisterTest(&register.Test{
117116
Name: "rhcos.services-disabled",

mantle/kola/tests/ostree/unlock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ func init() {
3131
Name: "ostree.unlock",
3232
Description: "Verify installing an rpm does not persist when using `ostree admin unlock`.",
3333
FailFast: true,
34-
Tags: []string{"ostree", kola.NeedsInternetTag, kola.BootcBaseTag}, // need network to pull RPM
34+
Tags: []string{"ostree", kola.NeedsInternetTag}, // need network to pull RPM
3535
})
3636
register.RegisterTest(&register.Test{
3737
Run: ostreeHotfixTest,
3838
ClusterSize: 1,
3939
Name: "ostree.hotfix",
4040
Description: "Verify that the deployment can be put into hotfix mode where RPMs installed with persist across reboots.",
4141
FailFast: true,
42-
Tags: []string{"ostree", kola.NeedsInternetTag, kola.BootcBaseTag}, // need network to pull RPM
42+
Tags: []string{"ostree", kola.NeedsInternetTag}, // need network to pull RPM
4343
})
4444
}
4545

mantle/kola/tests/rpmostree/deployments.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func init() {
3333
Name: "rpmostree.upgrade-rollback",
3434
Description: "Verify an upgrade and rollback with a simulated update works.",
3535
FailFast: true,
36-
Tags: []string{"rpm-ostree", "upgrade", kola.BootcBaseTag},
36+
Tags: []string{"rpm-ostree", "upgrade"},
3737
})
3838
register.RegisterTest(&register.Test{
3939
Run: rpmOstreeInstallUninstall,

mantle/platform/machine/qemu/cluster.go

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -46,39 +46,48 @@ func (qc *Cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error)
4646
return qc.NewMachineWithOptions(userdata, platform.MachineOptions{})
4747
}
4848

49-
func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platform.MachineOptions) (platform.Machine, error) {
50-
if options.InstanceType != "" {
51-
return nil, errors.New("platform qemu does not support changing instance types")
49+
// ensureBuilderDefaults applies flight-level QEMU settings to a builder.
50+
func (qc *Cluster) ensureBuilderDefaults(builder *platform.QemuBuilder) (*platform.QemuBuilder, error) {
51+
if builder == nil {
52+
builder = platform.NewQemuBuilder()
5253
}
53-
id := uuid.New()
54-
55-
dir := filepath.Join(qc.RuntimeConf().OutputDir, id)
56-
if err := os.Mkdir(dir, 0777); err != nil {
57-
return nil, err
54+
if qc.flight.opts.Arch != "" {
55+
if err := builder.SetArchitecture(qc.flight.opts.Arch); err != nil {
56+
return nil, err
57+
}
58+
}
59+
if qc.flight.opts.Firmware != "" {
60+
builder.Firmware = qc.flight.opts.Firmware
5861
}
62+
builder.Swtpm = qc.flight.opts.Swtpm
63+
return builder, nil
64+
}
5965

66+
// createMachine prepares a machine and configures Ignition or SMBIOS SSH credentials
67+
// on the builder. When --no-ignition is set, userdata may be nil.
68+
func (qc *Cluster) createMachine(builder *platform.QemuBuilder, userdata *conf.UserData, options platform.MachineOptions, dir, id string) (*machine, *conf.Conf, error) {
6069
rconf := qc.RuntimeConf()
6170
noIgnition := rconf.NoIgnition
6271

6372
var config *conf.Conf
6473
if noIgnition {
6574
if qc.flight.opts.SecureExecution {
66-
return nil, errors.New("secure execution requires Ignition; not supported with --no-ignition")
75+
return nil, nil, errors.New("secure execution requires Ignition; not supported with --no-ignition")
6776
}
6877
if len(append(qc.flight.opts.BindRO, options.BindMountHostRO...)) > 0 {
69-
return nil, errors.New("bind mounts require Ignition; not supported with --no-ignition")
78+
return nil, nil, errors.New("bind mounts require Ignition; not supported with --no-ignition")
7079
}
7180
} else {
7281
var err error
7382
config, err = qc.RenderUserDataIfNeeded(userdata)
7483
if err != nil {
75-
return nil, err
84+
return nil, nil, err
7685
}
7786
}
7887

7988
journal, err := platform.NewJournal(dir)
8089
if err != nil {
81-
return nil, err
90+
return nil, nil, err
8291
}
8392

8493
qm := &machine{
@@ -88,14 +97,9 @@ func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
8897
consolePath: filepath.Join(dir, "console.txt"),
8998
}
9099

91-
builder := platform.NewQemuBuilder()
92-
if options.DisablePDeathSig {
93-
builder.Pdeathsig = false
94-
}
95-
96100
if qc.flight.opts.SecureExecution {
97101
if err := builder.SetSecureExecution(qc.flight.opts.SecureExecutionIgnitionPubKey, qc.flight.opts.SecureExecutionHostKey, config); err != nil {
98-
return nil, err
102+
return nil, nil, err
99103
}
100104
}
101105

@@ -110,53 +114,77 @@ func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
110114
for _, mountpair := range append(qc.flight.opts.BindRO, options.BindMountHostRO...) {
111115
src, dest, err := platform.ParseBindOpt(mountpair)
112116
if err != nil {
113-
return nil, err
117+
return nil, nil, err
114118
}
115119
readonly := true
116120
builder.MountHost(src, dest, readonly)
117-
config.MountHost(dest, readonly)
121+
if config != nil {
122+
config.MountHost(dest, readonly)
123+
}
118124
}
119125

120126
if noIgnition {
121127
keys, err := qc.Keys()
122128
if err != nil {
123-
return nil, err
129+
return nil, nil, err
124130
}
125131
smbios, err := platform.SystemdSMBIOSSSHCredential(rconf.SSHUser, keys)
126132
if err != nil {
127-
return nil, err
133+
return nil, nil, err
128134
}
129135
builder.Smbios = append(builder.Smbios, smbios)
130136
} else {
131137
builder.SetConfig(config)
132138
}
133-
defer builder.Close()
134-
builder.UUID = qm.id
135-
if qc.flight.opts.Arch != "" {
136-
if err := builder.SetArchitecture(qc.flight.opts.Arch); err != nil {
137-
return nil, err
138-
}
139+
140+
return qm, config, nil
141+
}
142+
143+
func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platform.MachineOptions) (platform.Machine, error) {
144+
if options.InstanceType != "" {
145+
return nil, errors.New("platform qemu does not support changing instance types")
139146
}
140-
if qc.flight.opts.Firmware != "" {
141-
builder.Firmware = qc.flight.opts.Firmware
147+
id := uuid.New()
148+
149+
dir := filepath.Join(qc.RuntimeConf().OutputDir, id)
150+
if err := os.Mkdir(dir, 0777); err != nil {
151+
return nil, err
142152
}
143-
builder.Swtpm = qc.flight.opts.Swtpm
144-
builder.Hostname = fmt.Sprintf("qemu%d", qc.BaseCluster.AllocateMachineSerial())
145-
builder.ConsoleFile = qm.consolePath
153+
154+
qemuBuilder := platform.NewQemuBuilder()
155+
if options.DisablePDeathSig {
156+
qemuBuilder.Pdeathsig = false
157+
}
158+
159+
var err error
160+
qemuBuilder, err = qc.ensureBuilderDefaults(qemuBuilder)
161+
if err != nil {
162+
return nil, err
163+
}
164+
165+
qm, _, err := qc.createMachine(qemuBuilder, userdata, options, dir, id)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
defer qemuBuilder.Close()
171+
qemuBuilder.UUID = qm.id
172+
qemuBuilder.ConsoleFile = qm.consolePath
173+
qemuBuilder.NumaNodes = options.NumaNodes
174+
qemuBuilder.Hostname = fmt.Sprintf("qemu%d", qc.BaseCluster.AllocateMachineSerial())
146175

147176
if qc.flight.opts.Memory != "" {
148177
memory, err := strconv.ParseInt(qc.flight.opts.Memory, 10, 32)
149178
if err != nil {
150179
return nil, errors.Wrapf(err, "parsing memory option")
151180
}
152-
builder.MemoryMiB = int(memory)
181+
qemuBuilder.MemoryMiB = int(memory)
153182
} else if options.MinMemory != 0 {
154-
builder.MemoryMiB = options.MinMemory
183+
qemuBuilder.MemoryMiB = options.MinMemory
155184
} else if qc.flight.opts.SecureExecution {
156-
builder.MemoryMiB = 4096 // SE needs at least 4GB
185+
qemuBuilder.MemoryMiB = 4096 // SE needs at least 4GB
157186
}
158187

159-
builder.NumaNodes = options.NumaNodes
160188
var primaryDisk platform.Disk
161189
if options.PrimaryDisk != "" {
162190
var diskp *platform.Disk
@@ -167,7 +195,7 @@ func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
167195
}
168196

169197
if qc.flight.opts.Cex || options.Cex {
170-
if err := builder.AddCexDevice(); err != nil {
198+
if err := qemuBuilder.AddCexDevice(); err != nil {
171199
return nil, err
172200
}
173201
}
@@ -194,38 +222,38 @@ func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
194222
primaryDisk.BackingFile = options.OverrideBackingFile
195223
}
196224

197-
if err = builder.AddBootDisk(&primaryDisk); err != nil {
225+
if err = qemuBuilder.AddBootDisk(&primaryDisk); err != nil {
198226
return nil, err
199227
}
200-
if err = builder.AddDisksFromSpecs(options.AdditionalDisks); err != nil {
228+
if err = qemuBuilder.AddDisksFromSpecs(options.AdditionalDisks); err != nil {
201229
return nil, err
202230
}
203231

204232
if len(options.HostForwardPorts) > 0 {
205-
builder.EnableUsermodeNetworking(options.HostForwardPorts, "")
233+
qemuBuilder.EnableUsermodeNetworking(options.HostForwardPorts, "")
206234
} else {
207235
h := []platform.HostForwardPort{
208236
{Service: "ssh", HostPort: 0, GuestPort: 22},
209237
}
210-
builder.EnableUsermodeNetworking(h, "")
238+
qemuBuilder.EnableUsermodeNetworking(h, "")
211239
}
212240
if options.AdditionalNics > 0 {
213-
builder.AddAdditionalNics(options.AdditionalNics)
241+
qemuBuilder.AddAdditionalNics(options.AdditionalNics)
214242
}
215243
if options.AppendKernelArgs != "" {
216-
builder.AppendKernelArgs = options.AppendKernelArgs
244+
qemuBuilder.AppendKernelArgs = options.AppendKernelArgs
217245
}
218246
if options.AppendFirstbootKernelArgs != "" {
219-
builder.AppendFirstbootKernelArgs = options.AppendFirstbootKernelArgs
247+
qemuBuilder.AppendFirstbootKernelArgs = options.AppendFirstbootKernelArgs
220248
}
221249
if !qc.RuntimeConf().InternetAccess {
222-
builder.RestrictNetworking = true
250+
qemuBuilder.RestrictNetworking = true
223251
}
224252
if options.Firmware != "" {
225-
builder.Firmware = options.Firmware
253+
qemuBuilder.Firmware = options.Firmware
226254
}
227255

228-
inst, err := builder.Exec()
256+
inst, err := qemuBuilder.Exec()
229257
if err != nil {
230258
return nil, err
231259
}

mantle/platform/machine/qemu/flight.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ type Options struct {
6060

6161
// NoIgnition skips Ignition; SSH keys are provisioned via systemd SMBIOS credentials.
6262
NoIgnition bool
63-
// SSHUser is the SSH login user when NoIgnition is set (default root).
64-
SSHUser string
6563

6664
*platform.Options
6765
}

mantle/platform/platform.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ type RuntimeConfig struct {
288288
// NoIgnition skips Ignition when launching QEMU VMs; SSH keys are
289289
// provisioned via systemd SMBIOS credentials instead.
290290
NoIgnition bool
291-
// SSHUser overrides the default SSH user (core) when set.
291+
// SSHUser overrides the default SSH user (core) when set (e.g. root with --no-ignition).
292292
SSHUser string
293293
}
294294

mantle/platform/smbios_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2026 Red Hat
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package platform
16+
17+
import (
18+
"crypto/ed25519"
19+
"crypto/rand"
20+
"encoding/base64"
21+
"strings"
22+
"testing"
23+
24+
"golang.org/x/crypto/ssh"
25+
"golang.org/x/crypto/ssh/agent"
26+
)
27+
28+
func TestSystemdSMBIOSSSHCredential(t *testing.T) {
29+
_, priv, err := ed25519.GenerateKey(rand.Reader)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
signer, err := ssh.NewSignerFromKey(priv)
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
pub := signer.PublicKey()
38+
keys := []*agent.Key{{Format: pub.Type(), Blob: pub.Marshal()}}
39+
40+
val, err := SystemdSMBIOSSSHCredential("root", keys)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
if !strings.HasPrefix(val, "type=11,value=io.systemd.credential.binary:tmpfiles.extra=") {
45+
t.Fatalf("unexpected smbios value prefix: %q", val)
46+
}
47+
48+
payloadB64 := strings.TrimPrefix(val, "type=11,value=io.systemd.credential.binary:tmpfiles.extra=")
49+
tmpfiles, err := base64.StdEncoding.DecodeString(payloadB64)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
if !strings.Contains(string(tmpfiles), "/root/.ssh") {
54+
t.Fatalf("tmpfiles missing root ssh dir: %q", tmpfiles)
55+
}
56+
if !strings.Contains(string(tmpfiles), "authorized_keys") {
57+
t.Fatalf("tmpfiles missing authorized_keys: %q", tmpfiles)
58+
}
59+
}
60+
61+
func TestSystemdSMBIOSSSHCredentialNoKeys(t *testing.T) {
62+
_, err := SystemdSMBIOSSSHCredential("root", nil)
63+
if err == nil {
64+
t.Fatal("expected error for empty keys")
65+
}
66+
}

0 commit comments

Comments
 (0)