@@ -17,6 +17,9 @@ package ignition
17
17
import (
18
18
"context"
19
19
"fmt"
20
+ "os"
21
+ "os/exec"
22
+ "regexp"
20
23
"time"
21
24
22
25
"github.com/pkg/errors"
@@ -26,6 +29,8 @@ import (
26
29
"github.com/coreos/coreos-assembler/mantle/kola/register"
27
30
"github.com/coreos/coreos-assembler/mantle/platform"
28
31
"github.com/coreos/coreos-assembler/mantle/platform/conf"
32
+ "github.com/coreos/coreos-assembler/mantle/util"
33
+ "github.com/coreos/ignition/v2/config/v3_2/types"
29
34
)
30
35
31
36
func init () {
@@ -37,6 +42,20 @@ func init() {
37
42
Platforms : []string {"qemu" },
38
43
Tags : []string {"ignition" },
39
44
})
45
+ register .RegisterTest (& register.Test {
46
+ Name : "coreos.unique.boot.failure" ,
47
+ ClusterSize : 0 ,
48
+ Description : "Verify boot fails if there are pre-existing boot filesystems." ,
49
+ Platforms : []string {"qemu" },
50
+ Run : runDualBootfsFailure ,
51
+ })
52
+ register .RegisterTest (& register.Test {
53
+ Name : "coreos.unique.boot.ignition.failure" ,
54
+ ClusterSize : 0 ,
55
+ Description : "Verify boot fails if there are pre-existing boot filesystems created with Ignition." ,
56
+ Platforms : []string {"qemu" },
57
+ Run : runDualBootfsIgnitionFailure ,
58
+ })
40
59
}
41
60
42
61
func runIgnitionFailure (c cluster.TestCluster ) {
@@ -45,47 +64,68 @@ func runIgnitionFailure(c cluster.TestCluster) {
45
64
}
46
65
}
47
66
48
- func ignitionFailure (c cluster.TestCluster ) error {
49
- // We can't create files in / due to the immutable bit OSTree creates, so
50
- // this is a convenient way to test Ignition failure.
51
- failConfig , err := conf .EmptyIgnition ().Render (conf .FailWarnings )
52
- if err != nil {
53
- return errors .Wrapf (err , "creating empty config" )
67
+ func runDualBootfsFailure (c cluster.TestCluster ) {
68
+ if err := dualBootfsFailure (c ); err != nil {
69
+ c .Fatal (err .Error ())
54
70
}
55
- failConfig . AddFile ( "/notwritable.txt" , "Hello world" , 0644 )
71
+ }
56
72
57
- builder := platform .NewQemuBuilder ()
58
- defer builder .Close ()
59
- builder .SetConfig (failConfig )
60
- err = builder .AddBootDisk (& platform.Disk {
61
- BackingFile : kola .QEMUOptions .DiskImage ,
62
- })
73
+ func runDualBootfsIgnitionFailure (c cluster.TestCluster ) {
74
+ if err := dualBootfsIgnitionFailure (c ); err != nil {
75
+ c .Fatal (err .Error ())
76
+ }
77
+ }
78
+
79
+ // Read file and verify if it contains a pattern
80
+ // 1. Read file, make sure it exists
81
+ // 2. regex for pattern
82
+ func fileContainsPattern (path string , searchPattern string ) (bool , error ) {
83
+ file , err := os .ReadFile (path )
63
84
if err != nil {
64
- return err
85
+ return false , err
65
86
}
66
- builder .MemoryMiB = 1024
67
- builder .Firmware = kola .QEMUOptions .Firmware
87
+ // File has content, but the pattern is not present
88
+ match := regexp .MustCompile (searchPattern ).Match (file )
89
+ if match {
90
+ // Pattern found
91
+ return true , nil
92
+ }
93
+ // Pattern not found
94
+ return false , nil
95
+ }
96
+
97
+ // Start the VM, take string and grep for it in the temporary console logs
98
+ func verifyError (builder * platform.QemuBuilder , searchPattern string ) error {
68
99
inst , err := builder .Exec ()
69
100
if err != nil {
70
101
return err
71
102
}
72
103
defer inst .Destroy ()
73
-
74
104
ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Minute )
105
+
75
106
defer cancel ()
76
107
77
108
errchan := make (chan error )
78
109
go func () {
79
- err := inst .WaitAll (ctx )
80
- if err == nil {
81
- err = fmt .Errorf ("Ignition unexpectedly succeeded" )
82
- } else if err == platform .ErrInitramfsEmergency {
83
- // The expected case
84
- err = nil
110
+ resultingError := inst .WaitAll (ctx )
111
+ if resultingError == nil {
112
+ resultingError = fmt .Errorf ("ignition unexpectedly succeeded" )
113
+ } else if resultingError == platform .ErrInitramfsEmergency {
114
+ // Expectred initramfs failure, checking the console file to insure
115
+ // that coreos.ignition.failure failed
116
+ found , err := fileContainsPattern (builder .ConsoleFile , searchPattern )
117
+ if err != nil {
118
+ resultingError = errors .Wrapf (err , "looking for pattern '%s' in file '%s' failed" , searchPattern , builder .ConsoleFile )
119
+ } else if ! found {
120
+ resultingError = errors .Wrapf (err , "pattern '%s' not found in file '%s'" , searchPattern , builder .ConsoleFile )
121
+ } else {
122
+ // The expected case
123
+ resultingError = nil
124
+ }
85
125
} else {
86
- err = errors .Wrapf (err , "expected initramfs emergency.target error" )
126
+ resultingError = errors .Wrapf (resultingError , "expected initramfs emergency.target error" )
87
127
}
88
- errchan <- err
128
+ errchan <- resultingError
89
129
}()
90
130
91
131
select {
@@ -101,3 +141,153 @@ func ignitionFailure(c cluster.TestCluster) error {
101
141
return nil
102
142
}
103
143
}
144
+
145
+ func ignitionFailure (c cluster.TestCluster ) error {
146
+ // We can't create files in / due to the immutable bit OSTree creates, so
147
+ // this is a convenient way to test Ignition failure.
148
+ failConfig , err := conf .EmptyIgnition ().Render (conf .FailWarnings )
149
+ if err != nil {
150
+ return errors .Wrapf (err , "creating empty config" )
151
+ }
152
+ failConfig .AddFile ("/notwritable.txt" , "Hello world" , 0644 )
153
+
154
+ builder := platform .NewQemuBuilder ()
155
+
156
+ // Create a temporary log file
157
+ consoleFile , err := builder .TempFile ("console.log" )
158
+ if err != nil {
159
+ return err
160
+ }
161
+ // Instruct builder to use it
162
+ builder .ConsoleFile = consoleFile .Name ()
163
+ defer builder .Close ()
164
+ builder .SetConfig (failConfig )
165
+ err = builder .AddBootDisk (& platform.Disk {
166
+ BackingFile : kola .QEMUOptions .DiskImage ,
167
+ })
168
+ if err != nil {
169
+ return err
170
+ }
171
+
172
+ builder .MemoryMiB = 1024
173
+ builder .Firmware = kola .QEMUOptions .Firmware
174
+
175
+ searchPattern := "error creating /sysroot/notwritable.txt"
176
+ if err := verifyError (builder , searchPattern ); err != nil {
177
+ return err
178
+ }
179
+ return nil
180
+ }
181
+
182
+ // Verify that there is only one boot filesystem attached to the device
183
+ func dualBootfsFailure (c cluster.TestCluster ) error {
184
+ builder := platform .NewQemuBuilder ()
185
+ // Create a temporary log file
186
+ consoleFile , err := builder .TempFile ("console.log" )
187
+ if err != nil {
188
+ return err
189
+ }
190
+ // Instruct builder to use it
191
+ builder .ConsoleFile = consoleFile .Name ()
192
+ // get current path and create tmp dir
193
+ fakeBootFile , err := builder .TempFile ("fakeBoot" )
194
+ if err != nil {
195
+ return err
196
+ }
197
+
198
+ // Truncate the file to 1 gigabyte
199
+ const oneGB = 1 << 30
200
+ err = fakeBootFile .Truncate (oneGB )
201
+ if err != nil {
202
+ return err
203
+ }
204
+
205
+ cmd := exec .Command ("mkfs.ext4" , "-L" , "boot" , fakeBootFile .Name ())
206
+ cmd .Stderr = os .Stderr
207
+ if err := cmd .Run (); err != nil {
208
+ c .Fatal (err )
209
+ }
210
+
211
+ err = builder .AddBootDisk (& platform.Disk {
212
+ BackingFile : kola .QEMUOptions .DiskImage ,
213
+ })
214
+ if err != nil {
215
+ return err
216
+ }
217
+ err = builder .AddDisk (& platform.Disk {
218
+ BackingFile : fakeBootFile .Name (),
219
+ BackingFormat : "raw" ,
220
+ })
221
+ if err != nil {
222
+ return err
223
+ }
224
+ builder .MemoryMiB = 1024
225
+ builder .Firmware = kola .QEMUOptions .Firmware
226
+
227
+ searchRegexString := "Error: System has 2 devices with a filesystem labeled 'boot'"
228
+ if err := verifyError (builder , searchRegexString ); err != nil {
229
+ return err
230
+ }
231
+ return nil
232
+ }
233
+
234
+ // Use ignition config to create a second bootfs
235
+ // 1 - produce an ignition file that format a disk with a"boot" label.
236
+ // 2 - boot the VM with the ignition file and an extra disk.
237
+ // 3 - observe the failure
238
+ func dualBootfsIgnitionFailure (c cluster.TestCluster ) error {
239
+ builder := platform .NewQemuBuilder ()
240
+
241
+ // Create a temporary log file
242
+ consoleFile , err := builder .TempFile ("console.log" )
243
+ if err != nil {
244
+ return err
245
+ }
246
+ // Instruct builder to use it
247
+ builder .ConsoleFile = consoleFile .Name ()
248
+ failConfig , err := conf .EmptyIgnition ().Render (conf .FailWarnings )
249
+ if err != nil {
250
+ return errors .Wrapf (err , "creating empty config" )
251
+ }
252
+
253
+ // Craft an Ignition file that formats a partition
254
+ formaterConfig := types.Config {
255
+ Ignition : types.Ignition {
256
+ Version : "3.2.0" ,
257
+ },
258
+ Storage : types.Storage {
259
+ Filesystems : []types.Filesystem {
260
+ {
261
+ Device : "/dev/disk/by-id/virtio-extra-boot" ,
262
+ Label : util .StrToPtr ("boot" ),
263
+ Format : util .StrToPtr ("vfat" ),
264
+ WipeFilesystem : util .BoolToPtr (true ),
265
+ },
266
+ },
267
+ },
268
+ }
269
+ failConfig .MergeV32 (formaterConfig )
270
+
271
+ builder .SetConfig (failConfig )
272
+ err = builder .AddBootDisk (& platform.Disk {
273
+ BackingFile : kola .QEMUOptions .DiskImage ,
274
+ })
275
+
276
+ if err != nil {
277
+ return err
278
+ }
279
+
280
+ err = builder .AddDisksFromSpecs ([]string {"1G:serial=extra-boot" })
281
+ if err != nil {
282
+ return err
283
+ }
284
+
285
+ builder .MemoryMiB = 1024
286
+ builder .Firmware = kola .QEMUOptions .Firmware
287
+
288
+ searchRegexString := "Error: System has 2 devices with a filesystem labeled 'boot'"
289
+ if err := verifyError (builder , searchRegexString ); err != nil {
290
+ return err
291
+ }
292
+ return nil
293
+ }
0 commit comments