@@ -17,6 +17,9 @@ package ignition
1717import (
1818 "context"
1919 "fmt"
20+ "os"
21+ "os/exec"
22+ "regexp"
2023 "time"
2124
2225 "github.com/pkg/errors"
@@ -26,6 +29,8 @@ import (
2629 "github.com/coreos/coreos-assembler/mantle/kola/register"
2730 "github.com/coreos/coreos-assembler/mantle/platform"
2831 "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"
2934)
3035
3136func init () {
@@ -37,6 +42,20 @@ func init() {
3742 Platforms : []string {"qemu" },
3843 Tags : []string {"ignition" },
3944 })
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+ })
4059}
4160
4261func runIgnitionFailure (c cluster.TestCluster ) {
@@ -45,47 +64,68 @@ func runIgnitionFailure(c cluster.TestCluster) {
4564 }
4665}
4766
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 ())
5470 }
55- failConfig . AddFile ( "/notwritable.txt" , "Hello world" , 0644 )
71+ }
5672
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 )
6384 if err != nil {
64- return err
85+ return false , err
6586 }
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 {
6899 inst , err := builder .Exec ()
69100 if err != nil {
70101 return err
71102 }
72103 defer inst .Destroy ()
73-
74104 ctx , cancel := context .WithTimeout (context .Background (), 2 * time .Minute )
105+
75106 defer cancel ()
76107
77108 errchan := make (chan error )
78109 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+ }
85125 } else {
86- err = errors .Wrapf (err , "expected initramfs emergency.target error" )
126+ resultingError = errors .Wrapf (resultingError , "expected initramfs emergency.target error" )
87127 }
88- errchan <- err
128+ errchan <- resultingError
89129 }()
90130
91131 select {
@@ -101,3 +141,153 @@ func ignitionFailure(c cluster.TestCluster) error {
101141 return nil
102142 }
103143}
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